@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
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isPlainObject as e } from "../../../core/isPlainObject.js";
|
|
2
|
+
import { MIXED as t } from "../../../core/mixed.js";
|
|
2
3
|
//#region src/features/text/runs/rangeStyle.ts
|
|
3
|
-
var
|
|
4
|
+
var n = [
|
|
4
5
|
"bold",
|
|
5
6
|
"fontWeight",
|
|
6
7
|
"italic",
|
|
@@ -17,66 +18,63 @@ var t = [
|
|
|
17
18
|
"fontScale",
|
|
18
19
|
"textTransform",
|
|
19
20
|
"fontVariantCaps"
|
|
20
|
-
],
|
|
21
|
+
], r = /* @__PURE__ */ new Set([
|
|
21
22
|
"bold",
|
|
22
23
|
"italic",
|
|
23
24
|
"underline",
|
|
24
25
|
"strikethrough",
|
|
25
26
|
"overline"
|
|
26
27
|
]);
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (r(e) && r(t)) {
|
|
34
|
-
let n = Object.keys(e);
|
|
35
|
-
return n.length === Object.keys(t).length && n.every((n) => n in t && i(e[n], t[n]));
|
|
28
|
+
function i(t, n) {
|
|
29
|
+
if (Object.is(t, n)) return !0;
|
|
30
|
+
if (Array.isArray(t) && Array.isArray(n)) return t.length === n.length && t.every((e, t) => i(e, n[t]));
|
|
31
|
+
if (e(t) && e(n)) {
|
|
32
|
+
let e = Object.keys(t);
|
|
33
|
+
return e.length === Object.keys(n).length && e.every((e) => e in n && i(t[e], n[e]));
|
|
36
34
|
}
|
|
37
35
|
return !1;
|
|
38
36
|
}
|
|
39
37
|
function a(e, t) {
|
|
40
|
-
let
|
|
41
|
-
return
|
|
38
|
+
let n = e[t];
|
|
39
|
+
return r.has(t) ? n ?? !1 : n;
|
|
42
40
|
}
|
|
43
41
|
var o = /* @__PURE__ */ new Set(["fill", "stroke"]);
|
|
44
42
|
function s(e, t, n) {
|
|
45
43
|
return o.has(e) ? i(t, n) : Object.is(t, n);
|
|
46
44
|
}
|
|
47
|
-
function c(e,
|
|
48
|
-
return
|
|
45
|
+
function c(e, t) {
|
|
46
|
+
return n.every((n) => s(n, a(e, n), a(t, n)));
|
|
49
47
|
}
|
|
50
48
|
function l(e) {
|
|
51
49
|
let t = 0;
|
|
52
50
|
for (let n of e) t += n.text.length;
|
|
53
51
|
return t;
|
|
54
52
|
}
|
|
55
|
-
function u(
|
|
53
|
+
function u(e, r, i) {
|
|
56
54
|
let o = Math.max(0, r), c = i;
|
|
57
55
|
if (!(o < c)) return {};
|
|
58
56
|
let l = /* @__PURE__ */ new Map(), u = /* @__PURE__ */ new Set(), d = !1, f = 0;
|
|
59
|
-
for (let
|
|
60
|
-
let
|
|
61
|
-
if (f =
|
|
62
|
-
for (let
|
|
63
|
-
let
|
|
64
|
-
d ? !u.has(
|
|
57
|
+
for (let t of e) {
|
|
58
|
+
let e = f + t.text.length, r = Math.max(f, o), i = Math.min(e, c);
|
|
59
|
+
if (f = e, r < i) {
|
|
60
|
+
for (let e of n) {
|
|
61
|
+
let n = a(t, e);
|
|
62
|
+
d ? !u.has(e) && !s(e, l.get(e), n) && u.add(e) : l.set(e, n);
|
|
65
63
|
}
|
|
66
64
|
d = !0;
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
67
|
if (!d) return {};
|
|
70
68
|
let p = {};
|
|
71
|
-
for (let
|
|
69
|
+
for (let e of n) if (u.has(e)) p[e] = t;
|
|
72
70
|
else {
|
|
73
|
-
let
|
|
74
|
-
|
|
71
|
+
let t = l.get(e);
|
|
72
|
+
t !== void 0 && (p[e] = t);
|
|
75
73
|
}
|
|
76
74
|
return p;
|
|
77
75
|
}
|
|
78
76
|
function d(e) {
|
|
79
|
-
return e.some((e) =>
|
|
77
|
+
return e.some((e) => n.some((t) => {
|
|
80
78
|
let n = e[t];
|
|
81
79
|
return n !== void 0 && n !== !1;
|
|
82
80
|
}));
|
|
@@ -91,13 +89,13 @@ function p(e, t) {
|
|
|
91
89
|
for (let [e, r] of Object.entries(t)) r !== void 0 && (n[e] = r);
|
|
92
90
|
return n;
|
|
93
91
|
}
|
|
94
|
-
function m(e,
|
|
95
|
-
let r = { ...e }, i = f(
|
|
92
|
+
function m(e, t) {
|
|
93
|
+
let r = { ...e }, i = f(t);
|
|
96
94
|
i !== void 0 && delete r[i];
|
|
97
|
-
for (let e of
|
|
98
|
-
if (!(e in
|
|
99
|
-
let
|
|
100
|
-
|
|
95
|
+
for (let e of n) {
|
|
96
|
+
if (!(e in t)) continue;
|
|
97
|
+
let n = t[e];
|
|
98
|
+
n === void 0 || n === !1 ? delete r[e] : r[e] = n;
|
|
101
99
|
}
|
|
102
100
|
return r;
|
|
103
101
|
}
|
|
@@ -130,6 +128,6 @@ function g(e, t, n, r) {
|
|
|
130
128
|
return h(o);
|
|
131
129
|
}
|
|
132
130
|
//#endregion
|
|
133
|
-
export {
|
|
131
|
+
export { t as MIXED, g as applyStyleToRange, p as patchRangeStyle, d as runsCarryStyling, u as styleAtRange, f as supersededKey };
|
|
134
132
|
|
|
135
133
|
//# sourceMappingURL=rangeStyle.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rangeStyle.js","names":["STYLE_KEYS","FLAG_KEYS","isPlainObject","v","deepEqual","a","b","item","i","keys","k","valueAt","value","run","key","STRUCTURAL_KEYS","sameValue","sameStyle","totalLength","n","runs","styleAtRange","lo","start","hi","end","first","mixed","seen","pos","runEnd","style","MIXED","runsCarryStyling","supersededKey","patch","patchRangeStyle","out","range","superseded","patchRun","next","coalesceRuns","prev","applyStyleToRange"],"sources":["../../../../src/features/text/runs/rangeStyle.ts"],"sourcesContent":["/**\n * Range-addressed styling over `StyledRun[]`. The panel addresses node paths;\n * a caret addresses a character range — these are the functions for the\n * second case. Pure and React-free so the semantics are unit-testable, the\n * same split `SelectionPanel` makes between `model.ts` and its component.\n *\n * Character offsets index the concatenated run text (`runsToPlainText`), and\n * a range is half-open: `[start, end)`. Ranges are clamped, never wrapped —\n * a negative `start` clamps to 0, an `end` past the text clamps to its\n * length, and a collapsed or inverted range (`start >= end`) is empty.\n * Offsets are UTF-16 code units, as everywhere else in this feature, so a\n * boundary can bisect a surrogate pair (`[1, 2)` over `'a👍b'` splits the\n * thumbs-up into lone surrogates). A caret-derived range never does this.\n *\n * **Run-level flags are additive over the node's `TextStyle`: a run can\n * turn `bold` / `italic` / `underline` / `strikethrough` on, never off.**\n * So a flag is stored only when true, and absent reads as `false`. A run\n * that must be lighter than a bold node says so with its own numeric\n * `fontWeight`, which `bold` is a preset over (see {@link supersededKey}).\n * For the boolean decorations there is no such primitive: both levels are\n * booleans, so a tri-state\n * (`true` / `false` / inherit) is expressible and we are collapsing it by\n * choice, to keep one canonical form per styling. Resolution must therefore\n * read decorations as `run.underline || style.underline` — a `??` would\n * make \"select a word in an underlined node, hit U to turn it off\" look\n * supported by the type while this file discards the `false` that expresses it.\n *\n * Copies here are shallow: a `fill` object is shared by reference between\n * the input runs, the patch, every run the patch touched, and the value\n * `styleAtRange` reports. Treat `fill` values as immutable.\n */\n\nimport { MIXED } from 'core/mixed';\nimport type { Mixed } from 'core/mixed';\nimport type { StyledRun } from '@weasel-js/text';\n\nexport { MIXED };\nexport type { Mixed };\n\n/** Every styleable key of a run, each either a concrete value or MIXED (the\n * runs in the range disagree at that key). */\nexport type RangeStyle = {\n [K in Exclude<keyof StyledRun, 'text'>]?: StyledRun[K] | Mixed;\n};\n\n/**\n * What `applyStyleToRange` writes. `text` is deliberately not part of it —\n * this is a styling operation, not an edit.\n */\nexport type RunStylePatch = Partial<Omit<StyledRun, 'text'>>;\n\n/** Every key of a run that carries styling — i.e. everything but `text`. */\nexport type StyleKey = Exclude<keyof StyledRun, 'text'>;\n\nconst STYLE_KEYS = [\n 'bold',\n 'fontWeight',\n 'italic',\n 'underline',\n 'strikethrough',\n 'overline',\n 'fontFamily',\n 'fontSize',\n 'letterSpacing',\n 'fill',\n 'stroke',\n 'script',\n 'baselineShift',\n 'fontScale',\n 'textTransform',\n 'fontVariantCaps',\n] as const satisfies readonly StyleKey[];\n\n// Every function here iterates STYLE_KEYS, so a key missing from the list is\n// invisible: `applyStyleToRange` would drop it when patching a run and\n// `sameStyle` would merge two runs that differ in it, silently discarding\n// one value. `satisfies` only checks the entries that ARE listed, so this\n// assertion checks the other direction — adding a key to `StyledRun`\n// without adding it here is a compile error.\ntype MissingStyleKey = Exclude<StyleKey, (typeof STYLE_KEYS)[number]>;\ntype AssertNoMissingStyleKey = [MissingStyleKey] extends [never] ? true : never;\nconst STYLE_KEYS_ARE_EXHAUSTIVE: AssertNoMissingStyleKey = true;\nvoid STYLE_KEYS_ARE_EXHAUSTIVE;\n\n/** The additive flags: absent and `false` mean the same thing, so both read as `false`. */\nconst FLAG_KEYS: ReadonlySet<StyleKey> = new Set<StyleKey>([\n 'bold',\n 'italic',\n 'underline',\n 'strikethrough',\n 'overline',\n]);\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/**\n * Structural equality over the JSON-ish values a run can hold. This assumes\n * `FillStyle` stays acyclic and plain — every member of the union is data\n * (`TextureHandle` is `{ id }`, so patterns compare by id, which is right).\n * A fill that grew a cyclic or class-valued field would recurse forever.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((item, i) => deepEqual(item, b[i]));\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n const keys = Object.keys(a);\n if (keys.length !== Object.keys(b).length) return false;\n return keys.every((k) => k in b && deepEqual(a[k], b[k]));\n }\n return false;\n}\n\n/** A run's value at `key`, with flags normalized so absent reads as `false`. */\nfunction valueAt(run: StyledRun, key: StyleKey): StyledRun[StyleKey] {\n const value = run[key];\n if (FLAG_KEYS.has(key)) return (value as boolean | undefined) ?? false;\n return value;\n}\n\n/** The structural values. Everything else is a primitive, and\n * `applyStyleToRange` spreads runs, so reference identity isn't reliable. */\nconst STRUCTURAL_KEYS: ReadonlySet<StyleKey> = new Set<StyleKey>(['fill', 'stroke']);\n\nfunction sameValue(key: StyleKey, a: unknown, b: unknown): boolean {\n return STRUCTURAL_KEYS.has(key) ? deepEqual(a, b) : Object.is(a, b);\n}\n\n/** Do two runs carry the same styling (their `text` aside)? */\nfunction sameStyle(a: StyledRun, b: StyledRun): boolean {\n return STYLE_KEYS.every((key) => sameValue(key, valueAt(a, key), valueAt(b, key)));\n}\n\n/** Total character length of a run array — the domain of its offsets. */\nfunction totalLength(runs: readonly StyledRun[]): number {\n let n = 0;\n for (const run of runs) n += run.text.length;\n return n;\n}\n\n/**\n * The styling shared by every run overlapping `[start, end)`: a concrete\n * value where the range agrees, `MIXED` where it doesn't. Keys no run\n * in range sets are absent (they inherit the node's `TextStyle`) — except\n * the additive flags, which read as `false` rather than `undefined` since a\n * run cannot un-set them. An empty range reads as `{}`. A reported `fill`\n * is the run's own object, not a copy — treat it as immutable.\n */\nexport function styleAtRange(\n runs: readonly StyledRun[],\n start: number,\n end: number,\n): RangeStyle {\n const lo = Math.max(0, start);\n const hi = end;\n // Negated `<` rather than `>=` so a non-numeric offset reads as empty\n // instead of matching every run.\n if (!(lo < hi)) return {};\n\n const first = new Map<StyleKey, unknown>();\n const mixed = new Set<StyleKey>();\n let seen = false;\n let pos = 0;\n\n for (const run of runs) {\n const runEnd = pos + run.text.length;\n const a = Math.max(pos, lo);\n const b = Math.min(runEnd, hi);\n pos = runEnd;\n if (!(a < b)) continue;\n\n for (const key of STYLE_KEYS) {\n const value = valueAt(run, key);\n if (!seen) first.set(key, value);\n else if (!mixed.has(key) && !sameValue(key, first.get(key), value)) {\n mixed.add(key);\n }\n }\n seen = true;\n }\n\n if (!seen) return {};\n\n const style: Record<string, unknown> = {};\n for (const key of STYLE_KEYS) {\n if (mixed.has(key)) style[key] = MIXED;\n else {\n const value = first.get(key);\n if (value !== undefined) style[key] = value;\n }\n }\n return style as RangeStyle;\n}\n\n/**\n * Does any run carry styling of its own? Per the additive-flags contract\n * above, absent and `false` are the same state — \"no run-level override\" —\n * so an array that is nothing but text reads as `false`, whether it came\n * from a plain-text edit or from styling that was toggled back off.\n *\n * This is how the commit path tells \"the edit produced run styling\" from\n * \"the edit produced text\", which is the question that decides whether a\n * node grows a `runs` array. Iterating `STYLE_KEYS` rather than the run's\n * own keys is what keeps it honest as `StyledRun` grows: a new styleable\n * key is a compile error at `STYLE_KEYS` until it's listed, and then this\n * sees it for free.\n */\nexport function runsCarryStyling(runs: readonly StyledRun[]): boolean {\n return runs.some((run) =>\n STYLE_KEYS.some((key) => {\n const value = run[key];\n return value !== undefined && value !== false;\n }),\n );\n}\n\n/**\n * The key a patch clears without naming it. `bold` is a preset over\n * `fontWeight`, and a run keeps at most one of the two: writing either drops\n * the other, so a Bold toggle is not hidden under a weight it cannot beat and\n * a picked weight is not shadowed by a stale flag. Naming both writes both.\n */\nexport function supersededKey(patch: RunStylePatch): 'bold' | 'fontWeight' | undefined {\n if ('bold' in patch && !('fontWeight' in patch)) return 'fontWeight';\n if (patch.fontWeight !== undefined && !('bold' in patch)) return 'bold';\n return undefined;\n}\n\n/**\n * `range` with `patch` laid over it, keyed as a write of `patch` would leave\n * the runs — for showing styling armed for the next keystroke over the\n * styling at the caret. A key the patch leaves `undefined` keeps the range's\n * value; the key it supersedes is dropped.\n */\nexport function patchRangeStyle(range: RangeStyle, patch: RunStylePatch): RangeStyle {\n const out: Record<string, unknown> = { ...range };\n const superseded = supersededKey(patch);\n if (superseded !== undefined) delete out[superseded];\n for (const [key, value] of Object.entries(patch)) {\n if (value !== undefined) out[key] = value;\n }\n return out as RangeStyle;\n}\n\n/** Apply `patch` to one run, deleting rather than storing \"no override\". */\nfunction patchRun(run: StyledRun, patch: RunStylePatch): StyledRun {\n const next: StyledRun = { ...run };\n const superseded = supersededKey(patch);\n if (superseded !== undefined) delete next[superseded];\n for (const key of STYLE_KEYS) {\n if (!(key in patch)) continue;\n const value = patch[key];\n // Per the additive-flags contract at the top of the file, `false` is not\n // a storable state — it means \"no run-level override\", same as\n // `undefined`. Deleting keeps one canonical form (coalescing already\n // treats absent and `false` as equal, so this isn't what makes runs\n // merge — it's what keeps the stored shape from drifting).\n if (value === undefined || value === false) delete next[key];\n else (next as unknown as Record<string, unknown>)[key] = value;\n }\n return next;\n}\n\n/** Merge neighbors that ended up styled identically; drop empty runs. */\nfunction coalesceRuns(runs: readonly StyledRun[]): StyledRun[] {\n const out: StyledRun[] = [];\n for (const run of runs) {\n if (run.text.length === 0) continue;\n const prev = out[out.length - 1];\n if (prev && sameStyle(prev, run)) prev.text += run.text;\n else out.push({ ...run });\n }\n return out;\n}\n\n/**\n * Write `patch` over `[start, end)`, splitting runs at the boundaries and\n * coalescing neighbors that end up identical. A patch value of `undefined`\n * or `false` deletes the key instead of storing it — see the additive-flags\n * contract at the top of the file.\n *\n * Returns a new array of new runs; neither the input array nor its runs are\n * mutated (though a nested `fill` is shared by reference). Normalization —\n * merging identical neighbors, dropping empty runs — applies to the whole\n * array, not just the patched span, and to every call, including one whose\n * range is empty and so patches nothing.\n */\nexport function applyStyleToRange(\n runs: readonly StyledRun[],\n start: number,\n end: number,\n patch: RunStylePatch,\n): StyledRun[] {\n const lo = Math.max(0, start);\n const hi = Math.min(totalLength(runs), end);\n // A collapsed caret is the common call, so it takes the same normalization\n // as any other range rather than a laxer copy-only path.\n if (!(lo < hi)) return coalesceRuns(runs);\n\n const out: StyledRun[] = [];\n let pos = 0;\n for (const run of runs) {\n const runEnd = pos + run.text.length;\n const a = Math.max(pos, lo);\n const b = Math.min(runEnd, hi);\n if (a < b) {\n if (pos < a) out.push({ ...run, text: run.text.slice(0, a - pos) });\n out.push(patchRun({ ...run, text: run.text.slice(a - pos, b - pos) }, patch));\n if (b < runEnd) out.push({ ...run, text: run.text.slice(b - pos) });\n } else {\n out.push({ ...run });\n }\n pos = runEnd;\n }\n\n return coalesceRuns(out);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rangeStyle.js","names":["STYLE_KEYS","FLAG_KEYS","deepEqual","a","b","item","i","isPlainObject","keys","k","valueAt","value","run","key","STRUCTURAL_KEYS","sameValue","sameStyle","totalLength","n","runs","styleAtRange","lo","start","hi","end","first","mixed","seen","pos","runEnd","style","MIXED","runsCarryStyling","supersededKey","patch","patchRangeStyle","out","range","superseded","patchRun","next","coalesceRuns","prev","applyStyleToRange"],"sources":["../../../../src/features/text/runs/rangeStyle.ts"],"sourcesContent":["/**\n * Range-addressed styling over `StyledRun[]`. The panel addresses node paths;\n * a caret addresses a character range — these are the functions for the\n * second case. Pure and React-free so the semantics are unit-testable, the\n * same split `SelectionPanel` makes between `model.ts` and its component.\n *\n * Character offsets index the concatenated run text (`runsToPlainText`), and\n * a range is half-open: `[start, end)`. Ranges are clamped, never wrapped —\n * a negative `start` clamps to 0, an `end` past the text clamps to its\n * length, and a collapsed or inverted range (`start >= end`) is empty.\n * Offsets are UTF-16 code units, as everywhere else in this feature, so a\n * boundary can bisect a surrogate pair (`[1, 2)` over `'a👍b'` splits the\n * thumbs-up into lone surrogates). A caret-derived range never does this.\n *\n * **Run-level flags are additive over the node's `TextStyle`: a run can\n * turn `bold` / `italic` / `underline` / `strikethrough` on, never off.**\n * So a flag is stored only when true, and absent reads as `false`. A run\n * that must be lighter than a bold node says so with its own numeric\n * `fontWeight`, which `bold` is a preset over (see {@link supersededKey}).\n * For the boolean decorations there is no such primitive: both levels are\n * booleans, so a tri-state\n * (`true` / `false` / inherit) is expressible and we are collapsing it by\n * choice, to keep one canonical form per styling. Resolution must therefore\n * read decorations as `run.underline || style.underline` — a `??` would\n * make \"select a word in an underlined node, hit U to turn it off\" look\n * supported by the type while this file discards the `false` that expresses it.\n *\n * Copies here are shallow: a `fill` object is shared by reference between\n * the input runs, the patch, every run the patch touched, and the value\n * `styleAtRange` reports. Treat `fill` values as immutable.\n */\n\nimport { isPlainObject } from 'core/isPlainObject';\nimport { MIXED } from 'core/mixed';\nimport type { Mixed } from 'core/mixed';\nimport type { StyledRun } from '@weasel-js/text';\n\nexport { MIXED };\nexport type { Mixed };\n\n/** Every styleable key of a run, each either a concrete value or MIXED (the\n * runs in the range disagree at that key). */\nexport type RangeStyle = {\n [K in Exclude<keyof StyledRun, 'text'>]?: StyledRun[K] | Mixed;\n};\n\n/**\n * What `applyStyleToRange` writes. `text` is deliberately not part of it —\n * this is a styling operation, not an edit.\n */\nexport type RunStylePatch = Partial<Omit<StyledRun, 'text'>>;\n\n/** Every key of a run that carries styling — i.e. everything but `text`. */\nexport type StyleKey = Exclude<keyof StyledRun, 'text'>;\n\nconst STYLE_KEYS = [\n 'bold',\n 'fontWeight',\n 'italic',\n 'underline',\n 'strikethrough',\n 'overline',\n 'fontFamily',\n 'fontSize',\n 'letterSpacing',\n 'fill',\n 'stroke',\n 'script',\n 'baselineShift',\n 'fontScale',\n 'textTransform',\n 'fontVariantCaps',\n] as const satisfies readonly StyleKey[];\n\n// Every function here iterates STYLE_KEYS, so a key missing from the list is\n// invisible: `applyStyleToRange` would drop it when patching a run and\n// `sameStyle` would merge two runs that differ in it, silently discarding\n// one value. `satisfies` only checks the entries that ARE listed, so this\n// assertion checks the other direction — adding a key to `StyledRun`\n// without adding it here is a compile error.\ntype MissingStyleKey = Exclude<StyleKey, (typeof STYLE_KEYS)[number]>;\ntype AssertNoMissingStyleKey = [MissingStyleKey] extends [never] ? true : never;\nconst STYLE_KEYS_ARE_EXHAUSTIVE: AssertNoMissingStyleKey = true;\nvoid STYLE_KEYS_ARE_EXHAUSTIVE;\n\n/** The additive flags: absent and `false` mean the same thing, so both read as `false`. */\nconst FLAG_KEYS: ReadonlySet<StyleKey> = new Set<StyleKey>([\n 'bold',\n 'italic',\n 'underline',\n 'strikethrough',\n 'overline',\n]);\n\n/**\n * Structural equality over the JSON-ish values a run can hold. This assumes\n * `FillStyle` stays acyclic and plain — every member of the union is data\n * (`TextureHandle` is `{ id }`, so patterns compare by id, which is right).\n * A fill that grew a cyclic field would recurse forever.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((item, i) => deepEqual(item, b[i]));\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n const keys = Object.keys(a);\n if (keys.length !== Object.keys(b).length) return false;\n return keys.every((k) => k in b && deepEqual(a[k], b[k]));\n }\n return false;\n}\n\n/** A run's value at `key`, with flags normalized so absent reads as `false`. */\nfunction valueAt(run: StyledRun, key: StyleKey): StyledRun[StyleKey] {\n const value = run[key];\n if (FLAG_KEYS.has(key)) return (value as boolean | undefined) ?? false;\n return value;\n}\n\n/** The structural values. Everything else is a primitive, and\n * `applyStyleToRange` spreads runs, so reference identity isn't reliable. */\nconst STRUCTURAL_KEYS: ReadonlySet<StyleKey> = new Set<StyleKey>(['fill', 'stroke']);\n\nfunction sameValue(key: StyleKey, a: unknown, b: unknown): boolean {\n return STRUCTURAL_KEYS.has(key) ? deepEqual(a, b) : Object.is(a, b);\n}\n\n/** Do two runs carry the same styling (their `text` aside)? */\nfunction sameStyle(a: StyledRun, b: StyledRun): boolean {\n return STYLE_KEYS.every((key) => sameValue(key, valueAt(a, key), valueAt(b, key)));\n}\n\n/** Total character length of a run array — the domain of its offsets. */\nfunction totalLength(runs: readonly StyledRun[]): number {\n let n = 0;\n for (const run of runs) n += run.text.length;\n return n;\n}\n\n/**\n * The styling shared by every run overlapping `[start, end)`: a concrete\n * value where the range agrees, `MIXED` where it doesn't. Keys no run\n * in range sets are absent (they inherit the node's `TextStyle`) — except\n * the additive flags, which read as `false` rather than `undefined` since a\n * run cannot un-set them. An empty range reads as `{}`. A reported `fill`\n * is the run's own object, not a copy — treat it as immutable.\n */\nexport function styleAtRange(\n runs: readonly StyledRun[],\n start: number,\n end: number,\n): RangeStyle {\n const lo = Math.max(0, start);\n const hi = end;\n // Negated `<` rather than `>=` so a non-numeric offset reads as empty\n // instead of matching every run.\n if (!(lo < hi)) return {};\n\n const first = new Map<StyleKey, unknown>();\n const mixed = new Set<StyleKey>();\n let seen = false;\n let pos = 0;\n\n for (const run of runs) {\n const runEnd = pos + run.text.length;\n const a = Math.max(pos, lo);\n const b = Math.min(runEnd, hi);\n pos = runEnd;\n if (!(a < b)) continue;\n\n for (const key of STYLE_KEYS) {\n const value = valueAt(run, key);\n if (!seen) first.set(key, value);\n else if (!mixed.has(key) && !sameValue(key, first.get(key), value)) {\n mixed.add(key);\n }\n }\n seen = true;\n }\n\n if (!seen) return {};\n\n const style: Record<string, unknown> = {};\n for (const key of STYLE_KEYS) {\n if (mixed.has(key)) style[key] = MIXED;\n else {\n const value = first.get(key);\n if (value !== undefined) style[key] = value;\n }\n }\n return style as RangeStyle;\n}\n\n/**\n * Does any run carry styling of its own? Per the additive-flags contract\n * above, absent and `false` are the same state — \"no run-level override\" —\n * so an array that is nothing but text reads as `false`, whether it came\n * from a plain-text edit or from styling that was toggled back off.\n *\n * This is how the commit path tells \"the edit produced run styling\" from\n * \"the edit produced text\", which is the question that decides whether a\n * node grows a `runs` array. Iterating `STYLE_KEYS` rather than the run's\n * own keys is what keeps it honest as `StyledRun` grows: a new styleable\n * key is a compile error at `STYLE_KEYS` until it's listed, and then this\n * sees it for free.\n */\nexport function runsCarryStyling(runs: readonly StyledRun[]): boolean {\n return runs.some((run) =>\n STYLE_KEYS.some((key) => {\n const value = run[key];\n return value !== undefined && value !== false;\n }),\n );\n}\n\n/**\n * The key a patch clears without naming it. `bold` is a preset over\n * `fontWeight`, and a run keeps at most one of the two: writing either drops\n * the other, so a Bold toggle is not hidden under a weight it cannot beat and\n * a picked weight is not shadowed by a stale flag. Naming both writes both.\n */\nexport function supersededKey(patch: RunStylePatch): 'bold' | 'fontWeight' | undefined {\n if ('bold' in patch && !('fontWeight' in patch)) return 'fontWeight';\n if (patch.fontWeight !== undefined && !('bold' in patch)) return 'bold';\n return undefined;\n}\n\n/**\n * `range` with `patch` laid over it, keyed as a write of `patch` would leave\n * the runs — for showing styling armed for the next keystroke over the\n * styling at the caret. A key the patch leaves `undefined` keeps the range's\n * value; the key it supersedes is dropped.\n */\nexport function patchRangeStyle(range: RangeStyle, patch: RunStylePatch): RangeStyle {\n const out: Record<string, unknown> = { ...range };\n const superseded = supersededKey(patch);\n if (superseded !== undefined) delete out[superseded];\n for (const [key, value] of Object.entries(patch)) {\n if (value !== undefined) out[key] = value;\n }\n return out as RangeStyle;\n}\n\n/** Apply `patch` to one run, deleting rather than storing \"no override\". */\nfunction patchRun(run: StyledRun, patch: RunStylePatch): StyledRun {\n const next: StyledRun = { ...run };\n const superseded = supersededKey(patch);\n if (superseded !== undefined) delete next[superseded];\n for (const key of STYLE_KEYS) {\n if (!(key in patch)) continue;\n const value = patch[key];\n // Per the additive-flags contract at the top of the file, `false` is not\n // a storable state — it means \"no run-level override\", same as\n // `undefined`. Deleting keeps one canonical form (coalescing already\n // treats absent and `false` as equal, so this isn't what makes runs\n // merge — it's what keeps the stored shape from drifting).\n if (value === undefined || value === false) delete next[key];\n else (next as unknown as Record<string, unknown>)[key] = value;\n }\n return next;\n}\n\n/** Merge neighbors that ended up styled identically; drop empty runs. */\nfunction coalesceRuns(runs: readonly StyledRun[]): StyledRun[] {\n const out: StyledRun[] = [];\n for (const run of runs) {\n if (run.text.length === 0) continue;\n const prev = out[out.length - 1];\n if (prev && sameStyle(prev, run)) prev.text += run.text;\n else out.push({ ...run });\n }\n return out;\n}\n\n/**\n * Write `patch` over `[start, end)`, splitting runs at the boundaries and\n * coalescing neighbors that end up identical. A patch value of `undefined`\n * or `false` deletes the key instead of storing it — see the additive-flags\n * contract at the top of the file.\n *\n * Returns a new array of new runs; neither the input array nor its runs are\n * mutated (though a nested `fill` is shared by reference). Normalization —\n * merging identical neighbors, dropping empty runs — applies to the whole\n * array, not just the patched span, and to every call, including one whose\n * range is empty and so patches nothing.\n */\nexport function applyStyleToRange(\n runs: readonly StyledRun[],\n start: number,\n end: number,\n patch: RunStylePatch,\n): StyledRun[] {\n const lo = Math.max(0, start);\n const hi = Math.min(totalLength(runs), end);\n // A collapsed caret is the common call, so it takes the same normalization\n // as any other range rather than a laxer copy-only path.\n if (!(lo < hi)) return coalesceRuns(runs);\n\n const out: StyledRun[] = [];\n let pos = 0;\n for (const run of runs) {\n const runEnd = pos + run.text.length;\n const a = Math.max(pos, lo);\n const b = Math.min(runEnd, hi);\n if (a < b) {\n if (pos < a) out.push({ ...run, text: run.text.slice(0, a - pos) });\n out.push(patchRun({ ...run, text: run.text.slice(a - pos, b - pos) }, patch));\n if (b < runEnd) out.push({ ...run, text: run.text.slice(b - pos) });\n } else {\n out.push({ ...run });\n }\n pos = runEnd;\n }\n\n return coalesceRuns(out);\n}\n"],"mappings":"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"}
|
|
@@ -1,59 +1,60 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { resolveAlign as n, resolveRuns as r, resolveTextStyle as i, textPoseLayoutInput as a } from "@weasel-js/text";
|
|
1
|
+
import { commandFill as e } from "../patterns/commandPaint.js";
|
|
2
|
+
import { resolveRuns as t, resolveTextStyle as n, textPoseLayoutInput as r } from "@weasel-js/text";
|
|
4
3
|
//#region src/features/text/textCommand.ts
|
|
5
|
-
function
|
|
6
|
-
let
|
|
4
|
+
function i(e, r, i, a, o, s, c, l, u, d = 1) {
|
|
5
|
+
let f = n(a, l, d);
|
|
7
6
|
return {
|
|
8
7
|
kind: "text",
|
|
9
8
|
x: e,
|
|
10
|
-
y:
|
|
11
|
-
runs:
|
|
12
|
-
align:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
y: r,
|
|
10
|
+
runs: t(i, f, d),
|
|
11
|
+
align: f.align,
|
|
12
|
+
direction: f.direction,
|
|
13
|
+
justify: f.align === "justify",
|
|
14
|
+
maxWidth: o,
|
|
15
|
+
style: a ?? {},
|
|
16
|
+
height: s,
|
|
17
|
+
verticalAlign: c,
|
|
18
|
+
width: u
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
function
|
|
22
|
-
return
|
|
21
|
+
function a(e, t, n, r, a, o, s, c, l, u = 1) {
|
|
22
|
+
return i(e, t, [{ text: n }], r, a, o, s, c, l, u);
|
|
23
23
|
}
|
|
24
|
-
function
|
|
25
|
-
let { runs: n, opts:
|
|
24
|
+
function o(e, t = 1) {
|
|
25
|
+
let { runs: n, opts: i } = r(e, t);
|
|
26
26
|
return {
|
|
27
27
|
kind: "text",
|
|
28
28
|
x: e.x,
|
|
29
29
|
y: e.y,
|
|
30
|
-
runs:
|
|
31
|
-
align:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
runs: s(n, e),
|
|
31
|
+
align: i.align,
|
|
32
|
+
direction: i.direction,
|
|
33
|
+
justify: i.justify,
|
|
34
|
+
maxWidth: i.maxWidth,
|
|
35
|
+
width: i.alignWidth,
|
|
35
36
|
style: e.style ?? {},
|
|
36
37
|
height: e.height,
|
|
37
38
|
verticalAlign: e.verticalAlign
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
|
-
function
|
|
41
|
-
return
|
|
42
|
-
let
|
|
41
|
+
function s(t, n) {
|
|
42
|
+
return t.every((e) => c(e.fill) && c(e.stroke?.paint)) ? t : t.map((t) => {
|
|
43
|
+
let r = t.fill && e(t.fill, n), i = t.stroke?.paint && e(t.stroke.paint, n);
|
|
43
44
|
return {
|
|
44
|
-
...
|
|
45
|
-
fill:
|
|
46
|
-
stroke:
|
|
47
|
-
...
|
|
48
|
-
paint:
|
|
45
|
+
...t,
|
|
46
|
+
fill: r,
|
|
47
|
+
stroke: t.stroke && {
|
|
48
|
+
...t.stroke,
|
|
49
|
+
paint: i || void 0
|
|
49
50
|
}
|
|
50
51
|
};
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
|
-
function
|
|
54
|
+
function c(e) {
|
|
54
55
|
return e == null || (e.fill ?? "solid") === "solid";
|
|
55
56
|
}
|
|
56
57
|
//#endregion
|
|
57
|
-
export {
|
|
58
|
+
export { a as textCommand, o as textCommandFromPose, i as textCommandFromRuns };
|
|
58
59
|
|
|
59
60
|
//# sourceMappingURL=textCommand.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textCommand.js","names":["textCommandFromRuns","resolved","resolveTextStyle","style","paint","scale","resolveRuns","runs","
|
|
1
|
+
{"version":3,"file":"textCommand.js","names":["textCommandFromRuns","resolved","resolveTextStyle","style","paint","scale","resolveRuns","runs","textCommand","x","y","maxWidth","height","verticalAlign","width","textCommandFromPose","textPoseLayoutInput","pose","runsInPoseFrame","opts","isSolid","r","fill","commandFill","box"],"sources":["../../../src/features/text/textCommand.ts"],"sourcesContent":["/**\n * Build a `TextDrawCommand`. `textCommandFromPose` is the command for a text\n * node — what `kit:text` and `createTextLayer` both emit. `textCommand` takes\n * a plain string and wraps it in one run; `textCommandFromRuns` takes the\n * styled runs directly, for text that has no pose. All three\n * resolve through `resolveTextStyle` + `resolveRuns`, so every emitter of a\n * text command derives `align` and per-run resolution the same way — a\n * caller that assembles the command by hand is one field away from a\n * silently different result.\n *\n * `paint` is the node's `data.fill` / `data.stroke`. A caller with no node —\n * a HUD widget, a debug overlay — states its color on the run instead.\n *\n * `scale` is the view scale a `{ px }` `fontSize` or `letterSpacing` resolves\n * against — for a world-space layer, the mean of `view.scale.x` and\n * `view.scale.y`. It has to be applied here and not at draw time, because a\n * screen-pixel size moves the glyph advances and so the wrap points.\n */\n\nimport type { DrawCommand, TextDrawCommand } from '../../renderer';\nimport type { TextPaint, TextPose, TextStyle } from '@weasel-js/text';\nimport { resolveTextStyle, textPoseLayoutInput } from '@weasel-js/text';\nimport { resolveRuns } from '@weasel-js/text';\nimport type { StyledRun } from '@weasel-js/text';\nimport type { TextVerticalAlign, ResolvedRun } from '@weasel-js/text';\nimport type { FillStyle } from '@weasel-js/paint';\nimport type { FillPoseBox } from '../../core/fillInPoseFrame';\nimport { commandFill } from '../patterns/commandPaint';\n\n/** Build a draw command from styled runs, for text with no pose. `style`\n * supplies the defaults each run inherits; the rest match `textCommand`. */\nexport function textCommandFromRuns(\n x: number,\n y: number,\n runs: readonly StyledRun[],\n style?: TextStyle,\n maxWidth?: number,\n height?: number,\n verticalAlign?: TextVerticalAlign,\n paint?: TextPaint,\n width?: number,\n scale = 1,\n): DrawCommand {\n const resolved = resolveTextStyle(style, paint, scale);\n return {\n kind: 'text',\n x,\n y,\n runs: resolveRuns(runs, resolved, scale),\n align: resolved.align,\n direction: resolved.direction,\n justify: resolved.align === 'justify',\n maxWidth,\n style: style ?? {},\n height,\n verticalAlign,\n width,\n };\n}\n\n/** Build a draw command for a single unstyled string. Text is laid out with\n * the registered font's metrics, wrapped at `maxWidth`, aligned across\n * `width` (default `maxWidth`) and within `height` when `verticalAlign` is\n * given. */\nexport function textCommand(\n x: number,\n y: number,\n text: string,\n style?: TextStyle,\n maxWidth?: number,\n height?: number,\n verticalAlign?: TextVerticalAlign,\n paint?: TextPaint,\n width?: number,\n scale = 1,\n): DrawCommand {\n return textCommandFromRuns(\n x, y, [{ text }], style, maxWidth, height, verticalAlign, paint, width, scale,\n );\n}\n\n/**\n * The draw command for a text pose. Its wrap width, alignment box and runs\n * come from `textPoseLayoutInput`, so the renderer lays it out into exactly\n * the lines `layoutTextPose` reports for the same pose.\n *\n * Each run's paints are resolved against the pose box the way every other\n * node's are — a `'bounds'` paint mapped onto it, a pattern spec swapped for\n * its texture — since the renderer can do neither.\n */\nexport function textCommandFromPose(pose: TextPose, scale = 1): TextDrawCommand {\n const { runs, opts } = textPoseLayoutInput(pose, scale);\n return {\n kind: 'text',\n x: pose.x,\n y: pose.y,\n runs: runsInPoseFrame(runs, pose),\n align: opts.align,\n direction: opts.direction,\n justify: opts.justify,\n maxWidth: opts.maxWidth,\n width: opts.alignWidth,\n style: pose.style ?? {},\n height: pose.height,\n verticalAlign: pose.verticalAlign,\n };\n}\n\nfunction runsInPoseFrame(runs: ResolvedRun[], box: FillPoseBox): ResolvedRun[] {\n if (runs.every((r) => isSolid(r.fill) && isSolid(r.stroke?.paint))) return runs;\n return runs.map((r) => {\n const fill = r.fill && commandFill(r.fill, box);\n const paint = r.stroke?.paint && commandFill(r.stroke.paint, box);\n return { ...r, fill, stroke: r.stroke && { ...r.stroke, paint: paint || undefined } };\n });\n}\n\nfunction isSolid(paint: FillStyle | null | undefined): boolean {\n return paint == null || (paint.fill ?? 'solid') === 'solid';\n}\n"],"mappings":";;;AA+BA,SAAgBA,EACd,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,IAAQ,GACK;CACb,IAAMC,IAAWC,EAAiBC,GAAOC,GAAOC,CAAK;CACrD,OAAO;EACL,MAAM;EACN;EACA;EACA,MAAMC,EAAYC,GAAMN,GAAUI,CAAK;EACvC,OAAOJ,EAAS;EAChB,WAAWA,EAAS;EACpB,SAASA,EAAS,UAAU;EAC5B;EACA,OAAOE,KAAS,CAAC;EACjB;EACA;EACA;CACF;AACF;AAMA,SAAgBK,EACd,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,IAAQ,GACK;CACb,OAAOR,EACLS,GAAGC,GAAG,CAAC,EAAE,QAAK,CAAC,GAAGP,GAAOQ,GAAUC,GAAQC,GAAeT,GAAOU,GAAOT,CAC1E;AACF;AAWA,SAAgBU,EAAoB,GAAgB,IAAQ,GAAoB;CAC9E,IAAM,EAAE,SAAM,YAASC,EAAoBC,GAAMZ,CAAK;CACtD,OAAO;EACL,MAAM;EACN,GAAGY,EAAK;EACR,GAAGA,EAAK;EACR,MAAMC,EAAgBX,GAAMU,CAAI;EAChC,OAAOE,EAAK;EACZ,WAAWA,EAAK;EAChB,SAASA,EAAK;EACd,UAAUA,EAAK;EACf,OAAOA,EAAK;EACZ,OAAOF,EAAK,SAAS,CAAC;EACtB,QAAQA,EAAK;EACb,eAAeA,EAAK;CACtB;AACF;AAEA,SAASC,EAAgB,GAAqB,GAAiC;CAE7E,OADIX,EAAK,OAAO,MAAMa,EAAQC,EAAE,IAAI,KAAKD,EAAQC,EAAE,QAAQ,KAAK,CAAC,IAAUd,IACpEA,EAAK,KAAK,MAAM;EACrB,IAAMe,IAAOD,EAAE,QAAQE,EAAYF,EAAE,MAAMG,CAAG,GACxCpB,IAAQiB,EAAE,QAAQ,SAASE,EAAYF,EAAE,OAAO,OAAOG,CAAG;EAChE,OAAO;GAAE,GAAGH;GAAG;GAAM,QAAQA,EAAE,UAAU;IAAE,GAAGA,EAAE;IAAQ,OAAOjB,KAAS,KAAA;GAAU;EAAE;CACtF,CAAC;AACH;AAEA,SAASgB,EAAQ,GAA8C;CAC7D,OAAOhB,KAAS,SAASA,EAAM,QAAQ,aAAa;AACtD"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { SYNTHETIC_ITALIC_RADIANS as e } from "../../renderer/syntheticItalic.js";
|
|
2
|
-
import {
|
|
2
|
+
import { poseFrameOf as t } from "../../core/geometry/poseFrame.js";
|
|
3
3
|
import { loadFontOutlines as n, outlineStatus as r, resolveFontVariant as i } from "@weasel-js/font";
|
|
4
|
-
import { PATH_L as a, PATH_M as o, PATH_Z as s, pathFromD as c, pathSignedArea as l, reversePath as u,
|
|
5
|
-
import { layoutRuns as
|
|
4
|
+
import { PATH_L as a, PATH_M as o, PATH_Z as s, pathFromD as c, pathSignedArea as l, reversePath as u, transformPath as d } from "@weasel-js/geom";
|
|
5
|
+
import { layoutRuns as f, textPoseLayoutInput as p, verticalAlignOffset as m } from "@weasel-js/text";
|
|
6
6
|
//#region src/features/text/textToPath.ts
|
|
7
|
-
var
|
|
7
|
+
var h = class extends Error {
|
|
8
8
|
reason;
|
|
9
9
|
family;
|
|
10
10
|
weight;
|
|
@@ -13,7 +13,7 @@ var g = class extends Error {
|
|
|
13
13
|
super(`textToPath: "${t}" ${n}/${r} — ${i}`), this.name = "TextOutlinesError", this.reason = e, this.family = t, this.weight = n, this.style = r;
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
|
-
function
|
|
16
|
+
function g(e) {
|
|
17
17
|
let t = /* @__PURE__ */ new Map(), n = (e, n, r) => {
|
|
18
18
|
t.set(`${e}|${n}|${r}`, {
|
|
19
19
|
family: e,
|
|
@@ -28,9 +28,9 @@ function _(e) {
|
|
|
28
28
|
}
|
|
29
29
|
return [...t.values()];
|
|
30
30
|
}
|
|
31
|
-
function
|
|
31
|
+
function _(e) {
|
|
32
32
|
let { fontFamily: t, fontWeight: a, fontStyle: o } = e, s = (e, n) => {
|
|
33
|
-
throw new
|
|
33
|
+
throw new h(e, t, a, o, n);
|
|
34
34
|
}, c = r(t, a, o);
|
|
35
35
|
(c === "idle" || c === "loading") && (n(t, {
|
|
36
36
|
weight: a,
|
|
@@ -44,12 +44,12 @@ function v(e) {
|
|
|
44
44
|
style: f
|
|
45
45
|
}), s("outlines-loading", `the outlines of ${u} ${d}/${f} have not loaded yet; await loadTextOutlines first.`)), p === "failed" && s("outlines-failed", `the outlines registered for ${u} ${d}/${f} failed to load.`);
|
|
46
46
|
}
|
|
47
|
-
var
|
|
48
|
-
function
|
|
49
|
-
let t =
|
|
50
|
-
return t || (t = c(e), l(t) < 0 && (t = u(t)),
|
|
47
|
+
var v = 4096, y = /* @__PURE__ */ new Map();
|
|
48
|
+
function b(e) {
|
|
49
|
+
let t = y.get(e);
|
|
50
|
+
return t || (t = c(e), l(t) < 0 && (t = u(t)), y.size >= v && y.clear(), y.set(e, t), t);
|
|
51
51
|
}
|
|
52
|
-
function
|
|
52
|
+
function x(e) {
|
|
53
53
|
let t = 0, n = 0;
|
|
54
54
|
for (let r of e) t += r.commands.length, n += r.coords.length;
|
|
55
55
|
let r = new Uint8Array(t), i = new Float32Array(n), a = 0, o = 0;
|
|
@@ -61,7 +61,7 @@ function S(e) {
|
|
|
61
61
|
fillRule: "nonzero"
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
-
function
|
|
64
|
+
function S(e, t, n, r) {
|
|
65
65
|
return {
|
|
66
66
|
kind: "polygon",
|
|
67
67
|
commands: new Uint8Array([
|
|
@@ -84,10 +84,10 @@ function C(e, t, n, r) {
|
|
|
84
84
|
fillRule: "nonzero"
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
-
function
|
|
87
|
+
function C(e, t) {
|
|
88
88
|
return JSON.stringify([e, t ?? null]);
|
|
89
89
|
}
|
|
90
|
-
function
|
|
90
|
+
function w(n, r, i) {
|
|
91
91
|
let a = {
|
|
92
92
|
x: r.x,
|
|
93
93
|
y: r.y,
|
|
@@ -99,10 +99,10 @@ function T(n, r, i) {
|
|
|
99
99
|
verticalAlign: n.verticalAlign,
|
|
100
100
|
fill: n.fill,
|
|
101
101
|
stroke: n.stroke
|
|
102
|
-
}, { runs: o, opts: s } =
|
|
103
|
-
for (let e of o)
|
|
102
|
+
}, { runs: o, opts: s } = p(a, i.scale ?? 1);
|
|
103
|
+
for (let e of o) _(e);
|
|
104
104
|
let c = [], l = /* @__PURE__ */ new Map(), u = o.map((e) => {
|
|
105
|
-
let t =
|
|
105
|
+
let t = C(e.fill, e.stroke), n = l.get(t);
|
|
106
106
|
n === void 0 && (n = c.length, l.set(t, n), c.push(e.stroke === void 0 ? { fill: e.fill } : {
|
|
107
107
|
fill: e.fill,
|
|
108
108
|
stroke: e.stroke
|
|
@@ -115,41 +115,41 @@ function T(n, r, i) {
|
|
|
115
115
|
color: `#${n.toString(16).padStart(6, "0")}`
|
|
116
116
|
}
|
|
117
117
|
};
|
|
118
|
-
}),
|
|
118
|
+
}), g = (e) => e !== null && "color" in e ? parseInt(e.color.slice(1), 16) : 0, v = f(u, {
|
|
119
119
|
...s,
|
|
120
120
|
outlineMinSize: 0
|
|
121
121
|
});
|
|
122
|
-
for (let e of
|
|
123
|
-
let
|
|
124
|
-
for (let t of
|
|
125
|
-
let n =
|
|
122
|
+
for (let e of v.groups) if (e.source !== "outline" && e.quads.length > 0) throw new h("no-outlines", e.family, e.weight, e.style, "a fallback face serving some of the text's characters has no outlines registered.");
|
|
123
|
+
let y = r.x, w = r.y + m(n.verticalAlign, r.height, v.bounds.height), T = c.map(() => []);
|
|
124
|
+
for (let t of v.groups) {
|
|
125
|
+
let n = T[g(t.fill)], r = t.synthetic.italic ? Math.tan(e) : 0;
|
|
126
126
|
for (let e of t.glyphs) {
|
|
127
127
|
let t = e.scale, i = [
|
|
128
128
|
t,
|
|
129
129
|
0,
|
|
130
130
|
-r * t,
|
|
131
131
|
t,
|
|
132
|
-
e.x +
|
|
133
|
-
e.baselineY +
|
|
132
|
+
e.x + y,
|
|
133
|
+
e.baselineY + w
|
|
134
134
|
];
|
|
135
|
-
n.push(
|
|
135
|
+
n.push(d(b(e.d), i));
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
-
for (let e of
|
|
139
|
-
let
|
|
138
|
+
for (let e of v.decorations) T[g(e.fill)].push(S(e.x0 + y, e.y0 + w, e.x1 + y, e.y1 + w));
|
|
139
|
+
let E = t(r);
|
|
140
140
|
return {
|
|
141
141
|
paints: c,
|
|
142
|
-
paths:
|
|
143
|
-
let t =
|
|
144
|
-
return !
|
|
142
|
+
paths: T.map((e) => {
|
|
143
|
+
let t = x(e);
|
|
144
|
+
return !E || t.commands.length === 0 ? t : d(t, E);
|
|
145
145
|
})
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
|
-
function
|
|
149
|
-
return
|
|
148
|
+
function T(e, t, n = {}) {
|
|
149
|
+
return x(w(e, t, n).paths);
|
|
150
150
|
}
|
|
151
|
-
function
|
|
152
|
-
let { paints: r, paths: i } =
|
|
151
|
+
function E(e, t, n = {}) {
|
|
152
|
+
let { paints: r, paths: i } = w(e, t, n), a = [];
|
|
153
153
|
return r.forEach((e, t) => {
|
|
154
154
|
i[t].commands.length > 0 && a.push({
|
|
155
155
|
...e,
|
|
@@ -157,7 +157,7 @@ function D(e, t, n = {}) {
|
|
|
157
157
|
});
|
|
158
158
|
}), a;
|
|
159
159
|
}
|
|
160
|
-
async function
|
|
160
|
+
async function D(e, t = {}) {
|
|
161
161
|
let r = {
|
|
162
162
|
x: 0,
|
|
163
163
|
y: 0,
|
|
@@ -166,16 +166,16 @@ async function O(e, t = {}) {
|
|
|
166
166
|
text: e.text,
|
|
167
167
|
runs: e.runs,
|
|
168
168
|
style: e.style
|
|
169
|
-
}, { runs: i } =
|
|
170
|
-
await Promise.all(
|
|
169
|
+
}, { runs: i } = p(r, t.scale ?? 1);
|
|
170
|
+
await Promise.all(g(i).map((e) => n(e.family, {
|
|
171
171
|
weight: e.weight,
|
|
172
172
|
style: e.style
|
|
173
|
-
}))), await Promise.all(
|
|
173
|
+
}))), await Promise.all(g(i).map((e) => n(e.family, {
|
|
174
174
|
weight: e.weight,
|
|
175
175
|
style: e.style
|
|
176
176
|
})));
|
|
177
177
|
}
|
|
178
178
|
//#endregion
|
|
179
|
-
export {
|
|
179
|
+
export { h as TextOutlinesError, D as loadTextOutlines, T as textToPath, E as textToPathsByPaint };
|
|
180
180
|
|
|
181
181
|
//# sourceMappingURL=textToPath.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textToPath.js","names":["TextOutlinesError","family","weight","style","detail","reason","facesOf","out","add","r","runs","resolveFontVariant","assertOutlinable","run","fail","fontFamily","fontWeight","fontStyle","requested","outlineStatus","loadFontOutlines","status","EM_CACHE_LIMIT","emCache","emGlyph","p","d","pathFromD","pathSignedArea","reversePath","concat","nc","nk","parts","commands","coords","ci","ki","ruleContour","PATH_M","PATH_L","PATH_Z","x0","y0","x1","y1","paintKey","fill","stroke","walkOutlines","textPose","pose","data","resolved","layoutOpts","textPoseLayoutInput","opts","paints","indexOf","key","i","_stroke","rest","groupOf","laid","layoutRuns","g","dx","dy","verticalAlignOffset","shear","SYNTHETIC_ITALIC_RADIANS","glyph","s","m","transformPath","rot","poseRotationOf","path","rotateAboutPoint","textToPath","textToPathsByPaint","paths","paint","loadTextOutlines","f"],"sources":["../../../src/features/text/textToPath.ts"],"sourcesContent":["/**\n * Text as path geometry: the glyph outlines a text node draws, placed in\n * world space and composed into one `Path`. What \"Create Outlines\" replaces a\n * text node with, and what a Boolean op reads when a text node is an operand.\n *\n * The walk is `layoutRuns`', so the glyphs land exactly where the renderer's\n * outline tier puts them — same pen positions, same baselines, same synthetic\n * italic shear — but with the tier forced on for every glyph whatever its\n * size, and with paint set aside: unfilled text has outlines too.\n * `textToPathsByPaint` is the same walk, split by the paint each glyph wears.\n */\n\nimport type { FillStyle, Stroke } from '@weasel-js/paint';\n\nimport {\n loadFontOutlines, outlineStatus, resolveFontVariant, type FontStyle,\n} from '@weasel-js/font';\nimport {\n layoutRuns, textPoseLayoutInput, verticalAlignOffset,\n type ResolvedRun, type StyledRun, type TextPose, type TextStyle, type TextVerticalAlign,\n} from '@weasel-js/text';\nimport {\n PATH_L, PATH_M, PATH_Z, pathFromD, pathSignedArea, reversePath, rotateAboutPoint,\n transformPath, type Mat3, type PolygonPath,\n} from '@weasel-js/geom';\nimport { poseRotationOf } from 'core/geometry/poseRotation';\nimport type { PathInWorldPose } from 'features/paths/pathInWorld';\nimport { SYNTHETIC_ITALIC_RADIANS } from 'renderer/syntheticItalic';\n\n/** The fields of a text node's data that decide its glyph geometry, and the\n * paint `textToPathsByPaint` splits it by. Paint never changes the geometry. */\nexport interface TextOutlineSource {\n text: string;\n runs?: readonly StyledRun[];\n style?: TextStyle;\n verticalAlign?: TextVerticalAlign;\n /** The node's `data.fill`: `null` is no fill, absent the default black. */\n fill?: FillStyle | null;\n /** The node's `data.stroke`: absent or `null` is no stroke. */\n stroke?: Stroke | null;\n}\n\n/** One paint's share of a text node's outlines — see {@link textToPathsByPaint}. */\nexport interface TextPaintPath {\n /** The resolved fill these glyphs wear: the run's own, else the node's.\n * `null` for unfilled text, painted by its stroke alone. */\n fill: FillStyle | null;\n /** The resolved stroke — the run's own, else the node's — or absent. */\n stroke?: Stroke;\n /** World-space outlines, as {@link textToPath} builds them. */\n path: PolygonPath;\n}\n\n/** A text node as {@link textToPath} reads it: its data and its pose. */\nexport interface TextNodeSource {\n data: TextOutlineSource;\n pose: PathInWorldPose;\n}\n\n/** Options for {@link textToPath}. */\nexport interface TextToPathOptions {\n /** The view scale a `{ px }` `fontSize` or `letterSpacing` resolves\n * against, as for `textCommandFromPose`. Default 1. */\n scale?: number;\n}\n\n/**\n * Why a text node has no outline geometry to give:\n *\n * - `'no-outlines'` — the face that serves it has no outlines registered\n * (`registerFontOutlines`), so there is no geometry to read.\n * - `'outlines-loading'` — registered but not parsed yet. `loadTextOutlines`\n * waits for it; the call has already started the load.\n * - `'outlines-failed'` — registered, and the bytes did not load or parse.\n * - `'synthetic-bold'` — the requested weight is faked from a lighter face.\n * The renderer thickens the distance field for that, and a path has no\n * field to thicken; emboldening geometry needs outline offsetting, which\n * the kit does not have. Converting would silently lighten the text.\n */\nexport type TextOutlinesFailure =\n | 'no-outlines'\n | 'outlines-loading'\n | 'outlines-failed'\n | 'synthetic-bold';\n\n/** Thrown by {@link textToPath} when some run of the text has no outline\n * geometry. Names the run's requested face. */\nexport class TextOutlinesError extends Error {\n readonly reason: TextOutlinesFailure;\n readonly family: string;\n readonly weight: number;\n readonly style: FontStyle;\n\n constructor(reason: TextOutlinesFailure, family: string, weight: number, style: FontStyle, detail: string) {\n super(`textToPath: \"${family}\" ${weight}/${style} — ${detail}`);\n this.name = 'TextOutlinesError';\n this.reason = reason;\n this.family = family;\n this.weight = weight;\n this.style = style;\n }\n}\n\n/** The face every run is set in, before and after variant resolution. */\nfunction facesOf(runs: readonly ResolvedRun[]): { family: string; weight: number; style: FontStyle }[] {\n const out = new Map<string, { family: string; weight: number; style: FontStyle }>();\n const add = (family: string, weight: number, style: FontStyle) => {\n out.set(`${family}|${weight}|${style}`, { family, weight, style });\n };\n for (const r of runs) {\n add(r.fontFamily, r.fontWeight, r.fontStyle);\n const { family, weight, style } = resolveFontVariant(r.fontFamily, r.fontWeight, r.fontStyle).resolved;\n add(family, weight, style);\n }\n return [...out.values()];\n}\n\nfunction assertOutlinable(run: ResolvedRun): void {\n const { fontFamily, fontWeight, fontStyle } = run;\n const fail = (reason: TextOutlinesFailure, detail: string): never => {\n throw new TextOutlinesError(reason, fontFamily, fontWeight, fontStyle, detail);\n };\n const requested = outlineStatus(fontFamily, fontWeight, fontStyle);\n if (requested === 'idle' || requested === 'loading') {\n void loadFontOutlines(fontFamily, { weight: fontWeight, style: fontStyle });\n fail('outlines-loading', 'its outlines have not loaded yet; await loadTextOutlines first.');\n }\n const r = resolveFontVariant(fontFamily, fontWeight, fontStyle);\n const { family, weight, style } = r.resolved;\n if (r.synthetic.bold) {\n fail('synthetic-bold', `drawn as a faux bold of ${family} ${weight}/${style}, which has no ` +\n 'geometry to embolden. Register outlines for the real bold face.');\n }\n if (r.source === 'outline') return;\n const status = outlineStatus(family, weight, style);\n if (status === null) {\n fail('no-outlines', `served by ${family} ${weight}/${style}, which has no outlines ` +\n 'registered. Call registerFontOutlines for it.');\n }\n if (status === 'idle' || status === 'loading') {\n void loadFontOutlines(family, { weight, style });\n fail('outlines-loading', `the outlines of ${family} ${weight}/${style} have not loaded yet; ` +\n 'await loadTextOutlines first.');\n }\n if (status === 'failed') {\n fail('outlines-failed', `the outlines registered for ${family} ${weight}/${style} failed to load.`);\n }\n}\n\n// Em-space glyph geometry, wound one way, keyed by the outline data itself so a\n// re-registered face cannot serve stale geometry. The cap bounds a document\n// that cycles through many faces.\nconst EM_CACHE_LIMIT = 4096;\nconst emCache = new Map<string, PolygonPath>();\n\n/** A glyph's em-space path, wound positive so glyphs from faces that disagree\n * on winding — TrueType against CFF — still fill their overlaps under\n * `'nonzero'`, and so do the decoration rules drawn across them. */\nfunction emGlyph(d: string): PolygonPath {\n let p = emCache.get(d);\n if (p) return p;\n p = pathFromD(d);\n if (pathSignedArea(p) < 0) p = reversePath(p);\n if (emCache.size >= EM_CACHE_LIMIT) emCache.clear();\n emCache.set(d, p);\n return p;\n}\n\nfunction concat(parts: readonly PolygonPath[]): PolygonPath {\n let nc = 0, nk = 0;\n for (const p of parts) { nc += p.commands.length; nk += p.coords.length; }\n const commands = new Uint8Array(nc);\n const coords = new Float32Array(nk);\n let ci = 0, ki = 0;\n for (const p of parts) {\n commands.set(p.commands, ci); ci += p.commands.length;\n coords.set(p.coords, ki); ki += p.coords.length;\n }\n return { kind: 'polygon', commands, coords, fillRule: 'nonzero' };\n}\n\nfunction ruleContour(x0: number, y0: number, x1: number, y1: number): PolygonPath {\n return {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]),\n coords: new Float32Array([x0, y0, x1, y0, x1, y1, x0, y1]),\n fillRule: 'nonzero',\n };\n}\n\n/** Paint identity for grouping. Paint is plain data, so structural equality\n * is exact — a gradient set on two runs separately is still one paint. */\nfunction paintKey(fill: FillStyle | null, stroke: Stroke | undefined): string {\n return JSON.stringify([fill, stroke ?? null]);\n}\n\n/**\n * The one glyph walk behind both entry points. Each resolved run is repainted\n * with an opaque solid whose color is its paint group's index, so the layout's\n * own groups and decoration rules come back labeled with it; nothing else in\n * the layout reads paint once `outlineMinSize` is 0, so the geometry is what\n * it would be with no paint at all.\n */\nfunction walkOutlines(\n data: TextOutlineSource,\n pose: PathInWorldPose,\n opts: TextToPathOptions,\n): { paints: { fill: FillStyle | null; stroke?: Stroke }[]; paths: PolygonPath[] } {\n const textPose: TextPose = {\n x: pose.x, y: pose.y, width: pose.width, height: pose.height,\n text: data.text,\n runs: data.runs as StyledRun[] | undefined,\n style: data.style,\n verticalAlign: data.verticalAlign,\n fill: data.fill,\n stroke: data.stroke,\n };\n const { runs: resolved, opts: layoutOpts } = textPoseLayoutInput(textPose, opts.scale ?? 1);\n for (const run of resolved) assertOutlinable(run);\n\n const paints: { fill: FillStyle | null; stroke?: Stroke }[] = [];\n const indexOf = new Map<string, number>();\n const runs = resolved.map((run): ResolvedRun => {\n const key = paintKey(run.fill, run.stroke);\n let i = indexOf.get(key);\n if (i === undefined) {\n i = paints.length;\n indexOf.set(key, i);\n paints.push(run.stroke !== undefined ? { fill: run.fill, stroke: run.stroke } : { fill: run.fill });\n }\n const { stroke: _stroke, ...rest } = run;\n return { ...rest, fill: { fill: 'solid', color: `#${i.toString(16).padStart(6, '0')}` } };\n });\n const groupOf = (fill: FillStyle | null): number =>\n fill !== null && 'color' in fill ? parseInt(fill.color.slice(1), 16) : 0;\n\n const laid = layoutRuns(runs, { ...layoutOpts, outlineMinSize: 0 });\n // A code point the run's own face lacks escalates to a fallback face, which\n // the per-run check above never saw.\n for (const g of laid.groups) {\n if (g.source !== 'outline' && g.quads.length > 0) {\n throw new TextOutlinesError('no-outlines', g.family, g.weight, g.style,\n 'a fallback face serving some of the text\\'s characters has no outlines registered.');\n }\n }\n\n const dx = pose.x;\n const dy = pose.y + verticalAlignOffset(data.verticalAlign, pose.height, laid.bounds.height);\n const parts: PolygonPath[][] = paints.map(() => []);\n for (const g of laid.groups) {\n const out = parts[groupOf(g.fill)]!;\n const shear = g.synthetic.italic ? Math.tan(SYNTHETIC_ITALIC_RADIANS) : 0;\n for (const glyph of g.glyphs) {\n const s = glyph.scale;\n // The renderer's placement (`mergeGlyphMeshes`): x = pen + (ex - ey·shear)·s.\n const m: Mat3 = [s, 0, -shear * s, s, glyph.x + dx, glyph.baselineY + dy];\n out.push(transformPath(emGlyph(glyph.d), m) as PolygonPath);\n }\n }\n for (const r of laid.decorations) {\n parts[groupOf(r.fill)]!.push(ruleContour(r.x0 + dx, r.y0 + dy, r.x1 + dx, r.y1 + dy));\n }\n\n const rot = poseRotationOf(pose);\n const paths = parts.map((p) => {\n const path = concat(p);\n if (!rot || path.commands.length === 0) return path;\n return transformPath(path, rotateAboutPoint(rot.cx, rot.cy, rot.rotation)) as PolygonPath;\n });\n return { paints, paths };\n}\n\n/**\n * The outline geometry of a text node, in world space: every glyph and every\n * underline, strikethrough and overline rule it draws, as one `'nonzero'`\n * compound path whose filled region is their union. Glyphs keep the font's\n * curves; overlapping contours are not merged — run the result through\n * `pathUnion` for that, at the cost of flattening the curves.\n *\n * `pose` is the node's box and rotation. Synthetic italic is sheared exactly\n * as the renderer shears it. Throws {@link TextOutlinesError} when any run's\n * face cannot supply outlines — never returns a partial or empty path for\n * that. Text with no ink (empty, or only spaces) returns an empty path.\n */\nexport function textToPath(\n data: TextOutlineSource,\n pose: PathInWorldPose,\n opts: TextToPathOptions = {},\n): PolygonPath {\n return concat(walkOutlines(data, pose, opts).paths);\n}\n\n/**\n * {@link textToPath}, split by paint: one path per distinct resolved fill and\n * stroke, each gathering the glyphs and decoration rules that wear it — a\n * rule takes the fill of the run it underlines. Groups come in the order\n * their paint first appears in the text; a paint whose runs put down no ink\n * has none. Together the paths cover exactly what `textToPath` covers.\n *\n * Paint resolves as the renderer resolves it: a run's `fill` / `stroke`, else\n * the node's `data.fill` / `data.stroke` from `data`, else the default black\n * fill and no stroke. Throws as `textToPath` does.\n */\nexport function textToPathsByPaint(\n data: TextOutlineSource,\n pose: PathInWorldPose,\n opts: TextToPathOptions = {},\n): TextPaintPath[] {\n const { paints, paths } = walkOutlines(data, pose, opts);\n const out: TextPaintPath[] = [];\n paints.forEach((paint, i) => {\n if (paths[i]!.commands.length > 0) out.push({ ...paint, path: paths[i]! });\n });\n return out;\n}\n\n/**\n * Load the outlines of every face `data` is set in — the requested faces and\n * the ones they resolve to — so a following {@link textToPath} does not fail\n * with `'outlines-loading'`. Resolves once each has loaded or failed.\n */\nexport async function loadTextOutlines(\n data: TextOutlineSource,\n opts: TextToPathOptions = {},\n): Promise<void> {\n const textPose: TextPose = {\n x: 0, y: 0, width: 0, height: 0,\n text: data.text,\n runs: data.runs as StyledRun[] | undefined,\n style: data.style,\n };\n const { runs } = textPoseLayoutInput(textPose, opts.scale ?? 1);\n await Promise.all(facesOf(runs).map((f) => loadFontOutlines(f.family, { weight: f.weight, style: f.style })));\n // An outline-only family resolves to its own face only once that face has\n // parsed; before, it resolved to whatever stood in. Ask again.\n await Promise.all(facesOf(runs).map((f) => loadFontOutlines(f.family, { weight: f.weight, style: f.style })));\n}\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"textToPath.js","names":["TextOutlinesError","family","weight","style","detail","reason","facesOf","out","add","r","runs","resolveFontVariant","assertOutlinable","run","fail","fontFamily","fontWeight","fontStyle","requested","outlineStatus","loadFontOutlines","status","EM_CACHE_LIMIT","emCache","emGlyph","p","d","pathFromD","pathSignedArea","reversePath","concat","nc","nk","parts","commands","coords","ci","ki","ruleContour","PATH_M","PATH_L","PATH_Z","x0","y0","x1","y1","paintKey","fill","stroke","walkOutlines","textPose","pose","data","resolved","layoutOpts","textPoseLayoutInput","opts","paints","indexOf","key","i","_stroke","rest","groupOf","laid","layoutRuns","g","dx","dy","verticalAlignOffset","shear","SYNTHETIC_ITALIC_RADIANS","glyph","s","m","transformPath","frame","poseFrameOf","path","textToPath","textToPathsByPaint","paths","paint","loadTextOutlines","f"],"sources":["../../../src/features/text/textToPath.ts"],"sourcesContent":["/**\n * Text as path geometry: the glyph outlines a text node draws, placed in\n * world space and composed into one `Path`. What \"Create Outlines\" replaces a\n * text node with, and what a Boolean op reads when a text node is an operand.\n *\n * The walk is `layoutRuns`', so the glyphs land exactly where the renderer's\n * outline tier puts them — same pen positions, same baselines, same synthetic\n * italic shear — but with the tier forced on for every glyph whatever its\n * size, and with paint set aside: unfilled text has outlines too.\n * `textToPathsByPaint` is the same walk, split by the paint each glyph wears.\n */\n\nimport type { FillStyle, Stroke } from '@weasel-js/paint';\n\nimport {\n loadFontOutlines, outlineStatus, resolveFontVariant, type FontStyle,\n} from '@weasel-js/font';\nimport {\n layoutRuns, textPoseLayoutInput, verticalAlignOffset,\n type ResolvedRun, type StyledRun, type TextPose, type TextStyle, type TextVerticalAlign,\n} from '@weasel-js/text';\nimport {\n PATH_L, PATH_M, PATH_Z, pathFromD, pathSignedArea, reversePath,\n transformPath, type Mat3, type PolygonPath,\n} from '@weasel-js/geom';\nimport { poseFrameOf } from 'core/geometry/poseFrame';\nimport type { PathInWorldPose } from 'features/paths/pathInWorld';\nimport { SYNTHETIC_ITALIC_RADIANS } from 'renderer/syntheticItalic';\n\n/** The fields of a text node's data that decide its glyph geometry, and the\n * paint `textToPathsByPaint` splits it by. Paint never changes the geometry. */\nexport interface TextOutlineSource {\n text: string;\n runs?: readonly StyledRun[];\n style?: TextStyle;\n verticalAlign?: TextVerticalAlign;\n /** The node's `data.fill`: `null` is no fill, absent the default black. */\n fill?: FillStyle | null;\n /** The node's `data.stroke`: absent or `null` is no stroke. */\n stroke?: Stroke | null;\n}\n\n/** One paint's share of a text node's outlines — see {@link textToPathsByPaint}. */\nexport interface TextPaintPath {\n /** The resolved fill these glyphs wear: the run's own, else the node's.\n * `null` for unfilled text, painted by its stroke alone. */\n fill: FillStyle | null;\n /** The resolved stroke — the run's own, else the node's — or absent. */\n stroke?: Stroke;\n /** World-space outlines, as {@link textToPath} builds them. */\n path: PolygonPath;\n}\n\n/** A text node as {@link textToPath} reads it: its data and its pose. */\nexport interface TextNodeSource {\n data: TextOutlineSource;\n pose: PathInWorldPose;\n}\n\n/** Options for {@link textToPath}. */\nexport interface TextToPathOptions {\n /** The view scale a `{ px }` `fontSize` or `letterSpacing` resolves\n * against, as for `textCommandFromPose`. Default 1. */\n scale?: number;\n}\n\n/**\n * Why a text node has no outline geometry to give:\n *\n * - `'no-outlines'` — the face that serves it has no outlines registered\n * (`registerFontOutlines`), so there is no geometry to read.\n * - `'outlines-loading'` — registered but not parsed yet. `loadTextOutlines`\n * waits for it; the call has already started the load.\n * - `'outlines-failed'` — registered, and the bytes did not load or parse.\n * - `'synthetic-bold'` — the requested weight is faked from a lighter face.\n * The renderer thickens the distance field for that, and a path has no\n * field to thicken; emboldening geometry needs outline offsetting, which\n * the kit does not have. Converting would silently lighten the text.\n */\nexport type TextOutlinesFailure =\n | 'no-outlines'\n | 'outlines-loading'\n | 'outlines-failed'\n | 'synthetic-bold';\n\n/** Thrown by {@link textToPath} when some run of the text has no outline\n * geometry. Names the run's requested face. */\nexport class TextOutlinesError extends Error {\n readonly reason: TextOutlinesFailure;\n readonly family: string;\n readonly weight: number;\n readonly style: FontStyle;\n\n constructor(reason: TextOutlinesFailure, family: string, weight: number, style: FontStyle, detail: string) {\n super(`textToPath: \"${family}\" ${weight}/${style} — ${detail}`);\n this.name = 'TextOutlinesError';\n this.reason = reason;\n this.family = family;\n this.weight = weight;\n this.style = style;\n }\n}\n\n/** The face every run is set in, before and after variant resolution. */\nfunction facesOf(runs: readonly ResolvedRun[]): { family: string; weight: number; style: FontStyle }[] {\n const out = new Map<string, { family: string; weight: number; style: FontStyle }>();\n const add = (family: string, weight: number, style: FontStyle) => {\n out.set(`${family}|${weight}|${style}`, { family, weight, style });\n };\n for (const r of runs) {\n add(r.fontFamily, r.fontWeight, r.fontStyle);\n const { family, weight, style } = resolveFontVariant(r.fontFamily, r.fontWeight, r.fontStyle).resolved;\n add(family, weight, style);\n }\n return [...out.values()];\n}\n\nfunction assertOutlinable(run: ResolvedRun): void {\n const { fontFamily, fontWeight, fontStyle } = run;\n const fail = (reason: TextOutlinesFailure, detail: string): never => {\n throw new TextOutlinesError(reason, fontFamily, fontWeight, fontStyle, detail);\n };\n const requested = outlineStatus(fontFamily, fontWeight, fontStyle);\n if (requested === 'idle' || requested === 'loading') {\n void loadFontOutlines(fontFamily, { weight: fontWeight, style: fontStyle });\n fail('outlines-loading', 'its outlines have not loaded yet; await loadTextOutlines first.');\n }\n const r = resolveFontVariant(fontFamily, fontWeight, fontStyle);\n const { family, weight, style } = r.resolved;\n if (r.synthetic.bold) {\n fail('synthetic-bold', `drawn as a faux bold of ${family} ${weight}/${style}, which has no ` +\n 'geometry to embolden. Register outlines for the real bold face.');\n }\n if (r.source === 'outline') return;\n const status = outlineStatus(family, weight, style);\n if (status === null) {\n fail('no-outlines', `served by ${family} ${weight}/${style}, which has no outlines ` +\n 'registered. Call registerFontOutlines for it.');\n }\n if (status === 'idle' || status === 'loading') {\n void loadFontOutlines(family, { weight, style });\n fail('outlines-loading', `the outlines of ${family} ${weight}/${style} have not loaded yet; ` +\n 'await loadTextOutlines first.');\n }\n if (status === 'failed') {\n fail('outlines-failed', `the outlines registered for ${family} ${weight}/${style} failed to load.`);\n }\n}\n\n// Em-space glyph geometry, wound one way, keyed by the outline data itself so a\n// re-registered face cannot serve stale geometry. The cap bounds a document\n// that cycles through many faces.\nconst EM_CACHE_LIMIT = 4096;\nconst emCache = new Map<string, PolygonPath>();\n\n/** A glyph's em-space path, wound positive so glyphs from faces that disagree\n * on winding — TrueType against CFF — still fill their overlaps under\n * `'nonzero'`, and so do the decoration rules drawn across them. */\nfunction emGlyph(d: string): PolygonPath {\n let p = emCache.get(d);\n if (p) return p;\n p = pathFromD(d);\n if (pathSignedArea(p) < 0) p = reversePath(p);\n if (emCache.size >= EM_CACHE_LIMIT) emCache.clear();\n emCache.set(d, p);\n return p;\n}\n\nfunction concat(parts: readonly PolygonPath[]): PolygonPath {\n let nc = 0, nk = 0;\n for (const p of parts) { nc += p.commands.length; nk += p.coords.length; }\n const commands = new Uint8Array(nc);\n const coords = new Float32Array(nk);\n let ci = 0, ki = 0;\n for (const p of parts) {\n commands.set(p.commands, ci); ci += p.commands.length;\n coords.set(p.coords, ki); ki += p.coords.length;\n }\n return { kind: 'polygon', commands, coords, fillRule: 'nonzero' };\n}\n\nfunction ruleContour(x0: number, y0: number, x1: number, y1: number): PolygonPath {\n return {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]),\n coords: new Float32Array([x0, y0, x1, y0, x1, y1, x0, y1]),\n fillRule: 'nonzero',\n };\n}\n\n/** Paint identity for grouping. Paint is plain data, so structural equality\n * is exact — a gradient set on two runs separately is still one paint. */\nfunction paintKey(fill: FillStyle | null, stroke: Stroke | undefined): string {\n return JSON.stringify([fill, stroke ?? null]);\n}\n\n/**\n * The one glyph walk behind both entry points. Each resolved run is repainted\n * with an opaque solid whose color is its paint group's index, so the layout's\n * own groups and decoration rules come back labeled with it; nothing else in\n * the layout reads paint once `outlineMinSize` is 0, so the geometry is what\n * it would be with no paint at all.\n */\nfunction walkOutlines(\n data: TextOutlineSource,\n pose: PathInWorldPose,\n opts: TextToPathOptions,\n): { paints: { fill: FillStyle | null; stroke?: Stroke }[]; paths: PolygonPath[] } {\n const textPose: TextPose = {\n x: pose.x, y: pose.y, width: pose.width, height: pose.height,\n text: data.text,\n runs: data.runs as StyledRun[] | undefined,\n style: data.style,\n verticalAlign: data.verticalAlign,\n fill: data.fill,\n stroke: data.stroke,\n };\n const { runs: resolved, opts: layoutOpts } = textPoseLayoutInput(textPose, opts.scale ?? 1);\n for (const run of resolved) assertOutlinable(run);\n\n const paints: { fill: FillStyle | null; stroke?: Stroke }[] = [];\n const indexOf = new Map<string, number>();\n const runs = resolved.map((run): ResolvedRun => {\n const key = paintKey(run.fill, run.stroke);\n let i = indexOf.get(key);\n if (i === undefined) {\n i = paints.length;\n indexOf.set(key, i);\n paints.push(run.stroke !== undefined ? { fill: run.fill, stroke: run.stroke } : { fill: run.fill });\n }\n const { stroke: _stroke, ...rest } = run;\n return { ...rest, fill: { fill: 'solid', color: `#${i.toString(16).padStart(6, '0')}` } };\n });\n const groupOf = (fill: FillStyle | null): number =>\n fill !== null && 'color' in fill ? parseInt(fill.color.slice(1), 16) : 0;\n\n const laid = layoutRuns(runs, { ...layoutOpts, outlineMinSize: 0 });\n // A code point the run's own face lacks escalates to a fallback face, which\n // the per-run check above never saw.\n for (const g of laid.groups) {\n if (g.source !== 'outline' && g.quads.length > 0) {\n throw new TextOutlinesError('no-outlines', g.family, g.weight, g.style,\n 'a fallback face serving some of the text\\'s characters has no outlines registered.');\n }\n }\n\n const dx = pose.x;\n const dy = pose.y + verticalAlignOffset(data.verticalAlign, pose.height, laid.bounds.height);\n const parts: PolygonPath[][] = paints.map(() => []);\n for (const g of laid.groups) {\n const out = parts[groupOf(g.fill)]!;\n const shear = g.synthetic.italic ? Math.tan(SYNTHETIC_ITALIC_RADIANS) : 0;\n for (const glyph of g.glyphs) {\n const s = glyph.scale;\n // The renderer's placement (`mergeGlyphMeshes`): x = pen + (ex - ey·shear)·s.\n const m: Mat3 = [s, 0, -shear * s, s, glyph.x + dx, glyph.baselineY + dy];\n out.push(transformPath(emGlyph(glyph.d), m) as PolygonPath);\n }\n }\n for (const r of laid.decorations) {\n parts[groupOf(r.fill)]!.push(ruleContour(r.x0 + dx, r.y0 + dy, r.x1 + dx, r.y1 + dy));\n }\n\n const frame = poseFrameOf(pose);\n const paths = parts.map((p) => {\n const path = concat(p);\n if (!frame || path.commands.length === 0) return path;\n return transformPath(path, frame) as PolygonPath;\n });\n return { paints, paths };\n}\n\n/**\n * The outline geometry of a text node, in world space: every glyph and every\n * underline, strikethrough and overline rule it draws, as one `'nonzero'`\n * compound path whose filled region is their union. Glyphs keep the font's\n * curves; overlapping contours are not merged — run the result through\n * `pathUnion` for that, at the cost of flattening the curves.\n *\n * `pose` is the node's box, rotation, and `transform`. Synthetic italic is sheared exactly\n * as the renderer shears it. Throws {@link TextOutlinesError} when any run's\n * face cannot supply outlines — never returns a partial or empty path for\n * that. Text with no ink (empty, or only spaces) returns an empty path.\n */\nexport function textToPath(\n data: TextOutlineSource,\n pose: PathInWorldPose,\n opts: TextToPathOptions = {},\n): PolygonPath {\n return concat(walkOutlines(data, pose, opts).paths);\n}\n\n/**\n * {@link textToPath}, split by paint: one path per distinct resolved fill and\n * stroke, each gathering the glyphs and decoration rules that wear it — a\n * rule takes the fill of the run it underlines. Groups come in the order\n * their paint first appears in the text; a paint whose runs put down no ink\n * has none. Together the paths cover exactly what `textToPath` covers.\n *\n * Paint resolves as the renderer resolves it: a run's `fill` / `stroke`, else\n * the node's `data.fill` / `data.stroke` from `data`, else the default black\n * fill and no stroke. Throws as `textToPath` does.\n */\nexport function textToPathsByPaint(\n data: TextOutlineSource,\n pose: PathInWorldPose,\n opts: TextToPathOptions = {},\n): TextPaintPath[] {\n const { paints, paths } = walkOutlines(data, pose, opts);\n const out: TextPaintPath[] = [];\n paints.forEach((paint, i) => {\n if (paths[i]!.commands.length > 0) out.push({ ...paint, path: paths[i]! });\n });\n return out;\n}\n\n/**\n * Load the outlines of every face `data` is set in — the requested faces and\n * the ones they resolve to — so a following {@link textToPath} does not fail\n * with `'outlines-loading'`. Resolves once each has loaded or failed.\n */\nexport async function loadTextOutlines(\n data: TextOutlineSource,\n opts: TextToPathOptions = {},\n): Promise<void> {\n const textPose: TextPose = {\n x: 0, y: 0, width: 0, height: 0,\n text: data.text,\n runs: data.runs as StyledRun[] | undefined,\n style: data.style,\n };\n const { runs } = textPoseLayoutInput(textPose, opts.scale ?? 1);\n await Promise.all(facesOf(runs).map((f) => loadFontOutlines(f.family, { weight: f.weight, style: f.style })));\n // An outline-only family resolves to its own face only once that face has\n // parsed; before, it resolved to whatever stood in. Ask again.\n await Promise.all(facesOf(runs).map((f) => loadFontOutlines(f.family, { weight: f.weight, style: f.style })));\n}\n\n"],"mappings":"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"}
|