@rasamjs/tools 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/API.md +122 -2
  2. package/CHANGELOG.md +9 -1
  3. package/README.md +37 -0
  4. package/RELEASING.md +16 -3
  5. package/dist/advanced/context.d.ts +15 -0
  6. package/dist/advanced/context.d.ts.map +1 -0
  7. package/dist/advanced/context.js +2 -0
  8. package/dist/advanced/context.js.map +1 -0
  9. package/dist/advanced/curves.d.ts +5 -0
  10. package/dist/advanced/curves.d.ts.map +1 -0
  11. package/dist/advanced/curves.js +65 -0
  12. package/dist/advanced/curves.js.map +1 -0
  13. package/dist/advanced/geometry.d.ts +17 -0
  14. package/dist/advanced/geometry.d.ts.map +1 -0
  15. package/dist/advanced/geometry.js +61 -0
  16. package/dist/advanced/geometry.js.map +1 -0
  17. package/dist/advanced/rotation.d.ts +4 -0
  18. package/dist/advanced/rotation.d.ts.map +1 -0
  19. package/dist/advanced/rotation.js +86 -0
  20. package/dist/advanced/rotation.js.map +1 -0
  21. package/dist/advanced/session.d.ts +5 -0
  22. package/dist/advanced/session.d.ts.map +1 -0
  23. package/dist/advanced/session.js +240 -0
  24. package/dist/advanced/session.js.map +1 -0
  25. package/dist/advanced/snapper.d.ts +29 -0
  26. package/dist/advanced/snapper.d.ts.map +1 -0
  27. package/dist/advanced/snapper.js +118 -0
  28. package/dist/advanced/snapper.js.map +1 -0
  29. package/dist/advanced/types.d.ts +30 -0
  30. package/dist/advanced/types.d.ts.map +1 -0
  31. package/dist/advanced/types.js +2 -0
  32. package/dist/advanced/types.js.map +1 -0
  33. package/dist/drawing.d.ts +5 -0
  34. package/dist/drawing.d.ts.map +1 -0
  35. package/dist/drawing.js +3 -0
  36. package/dist/drawing.js.map +1 -0
  37. package/dist/editor/draw-tool.d.ts +35 -0
  38. package/dist/editor/draw-tool.d.ts.map +1 -0
  39. package/dist/editor/draw-tool.js +174 -0
  40. package/dist/editor/draw-tool.js.map +1 -0
  41. package/dist/editor/path-editor.d.ts +34 -0
  42. package/dist/editor/path-editor.d.ts.map +1 -0
  43. package/dist/editor/path-editor.js +208 -0
  44. package/dist/editor/path-editor.js.map +1 -0
  45. package/dist/editor/pointer.d.ts +21 -0
  46. package/dist/editor/pointer.d.ts.map +1 -0
  47. package/dist/editor/pointer.js +86 -0
  48. package/dist/editor/pointer.js.map +1 -0
  49. package/dist/editor/surface.d.ts +31 -0
  50. package/dist/editor/surface.d.ts.map +1 -0
  51. package/dist/editor/surface.js +138 -0
  52. package/dist/editor/surface.js.map +1 -0
  53. package/dist/grid.d.ts +23 -0
  54. package/dist/grid.d.ts.map +1 -0
  55. package/dist/grid.js +84 -0
  56. package/dist/grid.js.map +1 -0
  57. package/dist/rulers.d.ts +30 -0
  58. package/dist/rulers.d.ts.map +1 -0
  59. package/dist/rulers.js +234 -0
  60. package/dist/rulers.js.map +1 -0
  61. package/dist/snapping/advanced.d.ts +3 -0
  62. package/dist/snapping/advanced.d.ts.map +1 -0
  63. package/dist/snapping/advanced.js +2 -0
  64. package/dist/snapping/advanced.js.map +1 -0
  65. package/dist/snapping/targets.d.ts +1 -0
  66. package/dist/snapping/targets.d.ts.map +1 -1
  67. package/dist/snapping/targets.js +5 -1
  68. package/dist/snapping/targets.js.map +1 -1
  69. package/package.json +24 -4
  70. package/src/advanced/context.ts +15 -0
  71. package/src/advanced/curves.ts +65 -0
  72. package/src/advanced/geometry.ts +74 -0
  73. package/src/advanced/rotation.ts +93 -0
  74. package/src/advanced/session.ts +276 -0
  75. package/src/advanced/snapper.ts +124 -0
  76. package/src/advanced/types.ts +31 -0
  77. package/src/drawing.ts +4 -0
  78. package/src/editor/draw-tool.ts +190 -0
  79. package/src/editor/path-editor.ts +224 -0
  80. package/src/editor/pointer.ts +85 -0
  81. package/src/editor/surface.ts +141 -0
  82. package/src/grid.ts +99 -0
  83. package/src/rulers.ts +245 -0
  84. package/src/snapping/advanced.ts +8 -0
  85. package/src/snapping/targets.ts +11 -2
package/API.md CHANGED
@@ -102,7 +102,7 @@ destroyed and removes the drawing-disposal subscription. It can be repeated safe
102
102
  It leaves the drawing and other snappers usable. Drawing disposal destroys its
103
103
  snappers automatically. Changing `enabled` cannot revive a destroyed instance.
104
104
 
105
- The only runtime export is `Snapper`; `SnapperOptions`, `SnapRequest` and `SnapSession`
105
+ The original `/snapping` entry's only runtime export is `Snapper`; `SnapperOptions`, `SnapRequest` and `SnapSession`
106
106
  are type exports. Imports are safe without a document. There is no package root export,
107
107
  and internal module subpaths are blocked.
108
108
 
@@ -182,7 +182,127 @@ const transformer = new Transformer(drawing, { snap: new Snapper(drawing, { grid
182
182
 
183
183
  The bridge is structural: transformer imports no tools runtime. Pointer acquisition
184
184
  starts one session for a move or eligible resize; finish, Escape, cancellation, detach
185
- and destruction end it. Keyboard, rotation and pivot gestures do not acquire sessions.
185
+ and destruction end it. With the original `Snapper`, keyboard, rotation and pivot
186
+ gestures do not acquire sessions.
186
187
  Ctrl/Cmd is forwarded on pointer samples and re-evaluated when held or released while
187
188
  stationary. Existing ratio, minimum-size, `boundBox` and commit-precision rules can
188
189
  alter the final alignment after pointer correction.
190
+
191
+ ## Advanced snapping entry
192
+
193
+ `@rasamjs/tools/snapping/advanced` exports `AdvancedSnapper` and the types
194
+ `AdvancedSnapperOptions`, `AdvancedSnapRequest`, `RotationSnapRequest`,
195
+ `RotationSnapSession` and `RulerGuide`. It implements the transformer's optional
196
+ `rotatedResize`, `keyboard` and `rotate()` capabilities while retaining the original
197
+ constructor, `begin`, `enabled`, `isDestroyed`, `update` and `destroy` lifecycle.
198
+ Existing `Snapper` and custom providers keep their original behavior.
199
+
200
+ Additional options:
201
+
202
+ | Option | Default | Contract |
203
+ | ------------------------------------ | ------- | -------------------------------------------------------------------------------------------------------- |
204
+ | `guides: () => Iterable<RulerGuide>` | None | Snapshot `{ axis: 'x' \| 'y', position: number }` in root drawing units at session start. |
205
+ | `curves: boolean` | `true` | Snap to path, line, polyline, polygon, circle and ellipse contours. Independent of `shapes`. |
206
+ | `curveResolution: number` | `0.25` | Positive finite sampling interval in initial client pixels, capped at 8,192 segments per subpath. |
207
+ | `angleTolerance: number` | `5` | Positive finite degrees for matching another target's root-space x-axis orientation, modulo 180 degrees. |
208
+ | `keyboard: boolean` | `true` | Opt the provider into transformer keyboard move/resize sessions. |
209
+
210
+ Resize correction follows the dragged anchor. An oblique side handle stays on its
211
+ one-dimensional frame axis; a corner can correct both root axes. Skew, reflection
212
+ and nonuniform parent transforms are supported. The transformer's ratio, centered,
213
+ minimum-size and bound-box constraints still take priority; positional guides verify
214
+ the accepted geometry before rendering.
215
+
216
+ `rotate({ nodes, client, corners, pivot })` returns a `RotationSnapSession` or `null`.
217
+ `pivot` and `corners` are client coordinates. Angles and target transforms are
218
+ snapshotted at session start; pointer radius is preserved. `angleSnapped` reports
219
+ whether the most recent pointer sample matched another target. Radius is preserved
220
+ in the gesture's parent coordinate system. A matched target
221
+ wins over the transformer's fixed angle stops; explicit Shift rotation steps retain
222
+ priority. Ctrl/Cmd bypass and `enabled: false` apply to rotation too.
223
+
224
+ Point requests may include `nodes` to exclude the edited path or other moving
225
+ geometry. Requests may include `keyboard: true`; keyboard sessions reject a snap
226
+ that would hold the edit at its origin or push it backward. Raw keyboard motion
227
+ accumulates independently of corrections so held arrows can leave an existing stop.
228
+
229
+ Curve targets are sampled from detached copies once per session and queried as pure
230
+ client-space segments. Separate `M` subpaths are never connected. Sampling is
231
+ approximate: very long contours hit the segment cap, and zooming after the snapshot
232
+ can change the screen-space approximation error. Use a smaller `curveResolution`
233
+ when appropriate or restrict `targets` in large scenes. Target edits do not change
234
+ an active snapshot. Invalid/unavailable native geometry is skipped.
235
+
236
+ ## Grid overlay
237
+
238
+ `@rasamjs/tools/grid` exports `GridOverlay` and `GridOptions`.
239
+
240
+ ```ts
241
+ const grid = new GridOverlay(drawing, {
242
+ size: 10,
243
+ origin: { x: 0, y: 0 },
244
+ stroke: '#d9dee8',
245
+ });
246
+ grid.enabled = false;
247
+ grid.update();
248
+ grid.destroy();
249
+ ```
250
+
251
+ `size` is positive and finite; `origin` is copied and must be finite. Grid lines follow
252
+ the current root mapping, including pan, zoom and CSS transforms. At extreme zoom-out
253
+ the renderer coarsens visible lines to powers-of-two multiples of `size` to keep the
254
+ path bounded; snapping still uses the configured grid size. It is an editor underlay,
255
+ with no managed nodes, exports or undo entries. `isDestroyed` is read-only.
256
+
257
+ ## Ruler guides
258
+
259
+ `@rasamjs/tools/rulers` exports `RulerGuides`, `RulerOptions` and `NamedGuide`.
260
+
261
+ ```ts
262
+ const rulers = new RulerGuides(drawing, { onChange: (guides) => saveGuides(guides) });
263
+ const id = rulers.add('x', 100);
264
+ rulers.move(id, 120);
265
+ rulers.remove(id);
266
+ rulers.clear();
267
+ ```
268
+
269
+ `guides` is a frozen snapshot of `{ id, axis, position }`. Pass
270
+ `guides: () => rulers.guides` to `AdvancedSnapper`. The left ruler creates vertical
271
+ (`x`) guides; the top ruler creates horizontal (`y`) guides. Drag an existing guide
272
+ to move it. Release over a ruler or outside the host to remove it; double-click also
273
+ removes it. Escape, blur, pointer cancellation and capture loss restore the previous
274
+ guide or discard a new one. Focused guide lines support Arrow keys (Shift: 10 units)
275
+ and Delete/Backspace. Enter/Space on a ruler creates a guide at the current view center.
276
+ Guides are application state, independent of model history. `update`, `isDestroyed`
277
+ and `destroy` follow the overlay lifecycle; mutation methods reject a destroyed owner.
278
+
279
+ ## Drawing and path editing
280
+
281
+ `@rasamjs/tools/drawing` exports `DrawTool` and `PathEditor`, plus their option,
282
+ handle and adapter types. Their `snapper` option is a structural `PointSnapper`:
283
+
284
+ ```ts
285
+ interface PointSnapper {
286
+ begin(request: { kind: 'point'; client: Point; nodes?: readonly AnyNode[] }): SnapSession | null;
287
+ }
288
+ ```
289
+
290
+ `DrawTool(drawing, { snapper, kind, paint?, onCreate? })` supports `rect`, `ellipse`,
291
+ `line` and `path`. A path drag creates an editable two-anchor path. The enabled tool
292
+ owns a transparent native pointer layer. Drawing previews remain outside the model;
293
+ release creates one managed shape in one neutral change group. Cancellation creates
294
+ no shape and no undo entry. Programmatic `begin(client, ignore?)`, `move(client,
295
+ ignore?)` and `end(cancelled?)` use client coordinates and the same snapping rules.
296
+ `end()` returns the created node or `null`. Set `enabled: false` to return pointer
297
+ ownership to selection/transformer. `onCreate` runs after the committed edit.
298
+ Active previews follow camera changes without adding temporary model nodes.
299
+
300
+ `PathEditor(drawing, { snapper, stroke? }).attach(node)` accepts a live `Path`,
301
+ `Polyline` or `Polygon` in this drawing. `attach(null)` disconnects it. `handles`
302
+ returns anchors and Bezier controls with their command/coordinate indexes; arcs
303
+ expose their endpoints. Native handles use pointer capture, keep geometry in local
304
+ units under parent transforms, and exclude the edited node from snapping targets.
305
+ `begin(handleIndex)`, `move(client, ignore?)`, and `end(cancelled?)` expose the same
306
+ editing lifecycle to integrations. Each gesture is one undo step; cancellation
307
+ restores the original attribute representation exactly. `update`, `isDestroyed`
308
+ and `destroy` release all observers, listeners and ownership.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0 — unreleased
3
+ ## 0.2.0 (unreleased)
4
+
5
+ - Add the optional advanced snapping entry: oblique resize anchors, target-relative
6
+ rotation, contour/segment targets, ruler targets and keyboard nudges.
7
+ - Add camera-aware grid and draggable ruler overlays, snapped drawing tools and
8
+ path/Bezier point editing with grouped undo and cancellation.
9
+ - Keep the original snapping entry within its existing 6 KiB budget.
10
+
11
+ ## 0.1.0 — 2026-10-31
4
12
 
5
13
  - Add a 1,000-target drag benchmark in all three engines (`bench:drag`, run by the
6
14
  release gate) that reports drag-start, snap-call and frame times without a hardware
package/README.md CHANGED
@@ -100,3 +100,40 @@ It combines selection, transformer, viewport and snapper, with toggles for snapp
100
100
  shapes, grid and spacing. The server uses port **4179** and refuses an occupied port.
101
101
 
102
102
  See [API.md](API.md), [RELEASING.md](RELEASING.md) and [CHANGELOG.md](CHANGELOG.md).
103
+
104
+ ## Complete editor snapping
105
+
106
+ The optional `@rasamjs/tools/snapping/advanced` entry adds oblique resize handles,
107
+ matching other shapes' rotation angles, curve/segment targets, ruler guides and
108
+ keyboard nudges. The original `/snapping` entry retains its 6 KiB budget and behavior.
109
+
110
+ ```ts
111
+ import { AdvancedSnapper } from '@rasamjs/tools/snapping/advanced';
112
+ import { GridOverlay } from '@rasamjs/tools/grid';
113
+ import { RulerGuides } from '@rasamjs/tools/rulers';
114
+ import { DrawTool, PathEditor } from '@rasamjs/tools/drawing';
115
+
116
+ const rulers = new RulerGuides(drawing);
117
+ const grid = new GridOverlay(drawing, { size: 10 });
118
+ const snapper = new AdvancedSnapper(drawing, {
119
+ grid: 10,
120
+ guides: () => rulers.guides,
121
+ curves: true,
122
+ });
123
+ const transformer = new Transformer(drawing, { snap: snapper });
124
+ const draw = new DrawTool(drawing, { kind: 'rect', snapper });
125
+ draw.enabled = false; // Enable while the rectangle tool is selected.
126
+ const pathEditor = new PathEditor(drawing, { snapper });
127
+ pathEditor.attach(drawing.path({ d: 'M 10 10 Q 80 100 160 10' }));
128
+ ```
129
+
130
+ Use a positioned, isolated host (`position: relative; isolation: isolate`) for these
131
+ native overlays. Include `grid`, `rulers`, `snapper` and `pathEditor` in the viewport's
132
+ `overlays` list, or call their `update()` methods after outside mapping changes.
133
+ All overlays stay outside SVG exports and history. Every tool owns its listeners,
134
+ observers and pointer capture and cleans them up on destruction of the drawing.
135
+
136
+ Run the [complete editor example](examples/advanced.html) with
137
+ `npm run dev -w @rasamjs/tools`, then open
138
+ `http://127.0.0.1:4179/examples/advanced.html`. It demonstrates all seven additions,
139
+ selection, camera controls, undo/redo and editable Bezier controls.
package/RELEASING.md CHANGED
@@ -7,7 +7,10 @@ are development dependencies for the example and tests.
7
7
  The snapping entry has a **6 KiB (6,144 bytes) gzip** budget with all core entry points
8
8
  external. The owner approved increasing Phase 1's 4 KiB cap during Phase 4 on 2026-10-31
9
9
  after guide rendering measured 4,544 gzip bytes, leaving room for Phase 5 spacing.
10
- There is no root entry or root bundle budget.
10
+ There is no root entry or root bundle budget. Optional entries have separate
11
+ caps: advanced snapping 9 KiB, grid 2.5 KiB, rulers 4 KiB and drawing/path editing
12
+ 5 KiB. All core entry points stay external; no optional entry may acquire runtime
13
+ selection, transformer, viewport or history dependencies.
11
14
 
12
15
  ```sh
13
16
  npm run build
@@ -35,8 +38,8 @@ guides and hides them on release, follows the corrected pointer exactly, records
35
38
  undo step and undoes to the exact original markup.
36
39
 
37
40
  Run the full workspace gate before merging when its other package ports are available.
38
- Do not run it alongside another session's gate. The owner has deferred the local full
39
- workspace gate pending coordination; a passing tools gate does not complete that check.
41
+ Do not run it alongside another session's gate. Earlier local port-coordination deferrals are resolved. A passing tools gate alone
42
+ does not replace the full workspace check.
40
43
  The shared runner registers tools after its development dependencies and rechecks source
41
44
  fingerprints after every package gate.
42
45
 
@@ -73,3 +76,13 @@ patterns, backgrounds, dragged/centered resize, tie rules, scale-dependent toler
73
76
  real undo and fixed-pixel markers. The 6 KiB cap remains unchanged.
74
77
  Phase 6 adds the drag benchmark and the example's shape, grid and spacing toggles,
75
78
  with an application-drawn grid. It changes no runtime source.
79
+
80
+ ## Advanced editor checks
81
+
82
+ The advanced editor example is tested with real ruler, drawing, resize, rotation and
83
+ path-handle gestures in Chromium, Firefox and WebKit. Cases cover contour projection,
84
+ separate path subpaths, self exclusion, keyboard progress, grouped undo, cancellation,
85
+ grid camera updates, export isolation and destruction. Unit tests cover the affine
86
+ resize constraint, guide snapshots, rotation radius, provider opt-ins and raw keyboard
87
+ accumulation. Packed consumers import all optional entries without a document and
88
+ compile a real advanced provider through the transformer's structural protocol.
@@ -0,0 +1,15 @@
1
+ import type { Svg } from '@rasamjs/core';
2
+ import type { ResolvedOptions } from '../snapping/options.js';
3
+ import type { GuideOverlay } from '../snapping/overlay.js';
4
+ import type { AdvancedSnapperOptions } from './types.js';
5
+ export interface SessionContext<Request> {
6
+ readonly drawing: Svg;
7
+ readonly options: ResolvedOptions;
8
+ readonly advanced: AdvancedSnapperOptions;
9
+ readonly overlay: GuideOverlay;
10
+ readonly request: Request;
11
+ readonly isEnabled: () => boolean;
12
+ readonly isCurrent: () => boolean;
13
+ readonly onEnd: () => void;
14
+ }
15
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/advanced/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,WAAW,cAAc,CAAC,OAAO;IACrC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;CAC5B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/advanced/context.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ import type { AnyNode, Svg } from '@rasamjs/core';
2
+ import type { Segment } from './geometry.js';
3
+ /** Detached geometry is sampled once: target edits never change an active session. */
4
+ export declare function curveSegments(drawing: Svg, nodes: readonly AnyNode[], resolution: number): Segment[];
5
+ //# sourceMappingURL=curves.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curves.d.ts","sourceRoot":"","sources":["../../src/advanced/curves.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAe,MAAM,eAAe,CAAC;AAG/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAgB7C,sFAAsF;AACtF,wBAAgB,aAAa,CAC3B,OAAO,EAAE,GAAG,EACZ,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,UAAU,EAAE,MAAM,GACjB,OAAO,EAAE,CAuCX"}
@@ -0,0 +1,65 @@
1
+ import { PathArray } from '@rasamjs/core';
2
+ import { Guards } from '@rasamjs/core/advanced';
3
+ import { clientMatrix } from './geometry.js';
4
+ function subpaths(geometry) {
5
+ if (geometry.localName !== 'path')
6
+ return [geometry];
7
+ const parts = [];
8
+ for (const command of new PathArray(geometry.getAttribute('d') ?? '').toArray()) {
9
+ if (command[0] === 'M')
10
+ parts.push([]);
11
+ parts.at(-1).push(command);
12
+ }
13
+ return parts.map((commands) => {
14
+ const path = geometry.cloneNode(false);
15
+ path.setAttribute('d', new PathArray(commands).toString());
16
+ return path;
17
+ });
18
+ }
19
+ /** Detached geometry is sampled once: target edits never change an active session. */
20
+ export function curveSegments(drawing, nodes, resolution) {
21
+ const root = drawing.worldMatrix().inverse(), screen = clientMatrix(drawing);
22
+ if (!root || !screen)
23
+ return [];
24
+ const segments = [];
25
+ for (const node of nodes) {
26
+ if (!Guards.isTransformable(node))
27
+ continue;
28
+ if (!['path', 'line', 'polyline', 'polygon', 'circle', 'ellipse'].includes(node.el.localName))
29
+ continue;
30
+ node.flush();
31
+ const geometry = node.el.cloneNode(false);
32
+ if (typeof geometry.getTotalLength !== 'function')
33
+ continue;
34
+ const matrix = root.multiply(node.worldMatrix());
35
+ const pixels = screen.multiply(matrix);
36
+ const stretch = Math.hypot(pixels.a, pixels.b, pixels.c, pixels.d);
37
+ let parts;
38
+ try {
39
+ parts = subpaths(geometry);
40
+ }
41
+ catch {
42
+ continue;
43
+ }
44
+ for (const part of parts) {
45
+ let length;
46
+ try {
47
+ length = part.getTotalLength();
48
+ }
49
+ catch {
50
+ continue;
51
+ }
52
+ if (!Number.isFinite(length) || length <= 0)
53
+ continue;
54
+ const count = Math.max(1, Math.min(8192, Math.ceil((length * stretch) / resolution)));
55
+ let previous = matrix.apply(part.getPointAtLength(0));
56
+ for (let index = 1; index <= count; index++) {
57
+ const next = matrix.apply(part.getPointAtLength((length * index) / count));
58
+ segments.push([previous, next]);
59
+ previous = next;
60
+ }
61
+ }
62
+ }
63
+ return segments;
64
+ }
65
+ //# sourceMappingURL=curves.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curves.js","sourceRoot":"","sources":["../../src/advanced/curves.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { Matrix } from '@rasamjs/core';
2
+ import type { Point, Svg } from '@rasamjs/core';
3
+ export type Segment = readonly [Point, Point];
4
+ export declare const add: (a: Point, b: Point) => Point;
5
+ export declare const subtract: (a: Point, b: Point) => Point;
6
+ export declare const scale: (p: Point, value: number) => Point;
7
+ /** The closest point on cached segments, optionally restricted to a resize line. */
8
+ export declare function nearestOnSegments(point: Point, segments: readonly Segment[], direction?: Point): Point | null;
9
+ /** Root-to-client mapping is read once per sample, never once per target segment. */
10
+ export declare function clientMatrix(drawing: Svg): Matrix | null;
11
+ export declare function frameConstraint(corners: readonly Point[], anchor: Point): {
12
+ point: Point;
13
+ direction: Point | undefined;
14
+ project(delta: Point): Point;
15
+ } | null;
16
+ export declare const angleDifference: (a: number, b: number, period?: number) => number;
17
+ //# sourceMappingURL=geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../../src/advanced/geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC9C,eAAO,MAAM,GAAG,MAAO,KAAK,KAAK,KAAK,KAAG,KAAyC,CAAC;AACnF,eAAO,MAAM,QAAQ,MAAO,KAAK,KAAK,KAAK,KAAG,KAAyC,CAAC;AACxF,eAAO,MAAM,KAAK,MAAO,KAAK,SAAS,MAAM,KAAG,KAA6C,CAAC;AAG9F,oFAAoF;AACpF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,SAAS,CAAC,EAAE,KAAK,GAChB,KAAK,GAAG,IAAI,CA2Bd;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAKxD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK;;;mBAWrD,KAAK,GAAG,KAAK;SAQ/B;AAED,eAAO,MAAM,eAAe,MAAO,MAAM,KAAK,MAAM,sBAAiB,MACC,CAAC"}
@@ -0,0 +1,61 @@
1
+ import { Matrix } from '@rasamjs/core';
2
+ export const add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
3
+ export const subtract = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
4
+ export const scale = (p, value) => ({ x: p.x * value, y: p.y * value });
5
+ const cross = (a, b) => a.x * b.y - a.y * b.x;
6
+ /** The closest point on cached segments, optionally restricted to a resize line. */
7
+ export function nearestOnSegments(point, segments, direction) {
8
+ let best = null, distance = Infinity;
9
+ for (const [a, b] of segments) {
10
+ const edge = subtract(b, a), relative = subtract(a, point);
11
+ let candidate;
12
+ const determinant = direction && cross(direction, edge);
13
+ if (direction && Math.abs(determinant) > 1e-12) {
14
+ const along = cross(relative, direction) / determinant;
15
+ if (along < 0 || along > 1)
16
+ continue;
17
+ candidate = add(point, scale(direction, cross(relative, edge) / determinant));
18
+ }
19
+ else {
20
+ if (direction && Math.abs(cross(relative, direction)) > 1e-9)
21
+ continue;
22
+ const length = edge.x * edge.x + edge.y * edge.y;
23
+ const t = length
24
+ ? Math.max(0, Math.min(1, ((point.x - a.x) * edge.x + (point.y - a.y) * edge.y) / length))
25
+ : 0;
26
+ candidate = add(a, scale(edge, t));
27
+ }
28
+ const next = Math.hypot(candidate.x - point.x, candidate.y - point.y);
29
+ if (next < distance) {
30
+ best = candidate;
31
+ distance = next;
32
+ }
33
+ }
34
+ return best;
35
+ }
36
+ /** Root-to-client mapping is read once per sample, never once per target segment. */
37
+ export function clientMatrix(drawing) {
38
+ const a = drawing.toClient({ x: 0, y: 0 });
39
+ const b = drawing.toClient({ x: 1, y: 0 });
40
+ const c = drawing.toClient({ x: 0, y: 1 });
41
+ return a && b && c ? new Matrix(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y, a.x, a.y) : null;
42
+ }
43
+ export function frameConstraint(corners, anchor) {
44
+ const [a, b, , d] = corners;
45
+ const u = subtract(b, a), v = subtract(d, a);
46
+ const inverse = new Matrix(u.x, u.y, v.x, v.y, 0, 0).inverse();
47
+ if (!inverse)
48
+ return null;
49
+ const point = add(a, add(scale(u, anchor.x), scale(v, anchor.y)));
50
+ const direction = anchor.x === 0.5 ? v : anchor.y === 0.5 ? u : undefined;
51
+ return {
52
+ point,
53
+ direction,
54
+ project(delta) {
55
+ const unit = inverse.apply(delta);
56
+ return add(point, add(scale(u, anchor.x === 0.5 ? 0 : unit.x), scale(v, anchor.y === 0.5 ? 0 : unit.y)));
57
+ },
58
+ };
59
+ }
60
+ export const angleDifference = (a, b, period = 360) => ((((a - b + period * 1.5) % period) + period) % period) - period / 2;
61
+ //# sourceMappingURL=geometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.js","sourceRoot":"","sources":["../../src/advanced/geometry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { SessionContext } from './context.js';
2
+ import type { RotationSnapRequest, RotationSnapSession } from './types.js';
3
+ export declare function createRotationSession(context: SessionContext<RotationSnapRequest>): RotationSnapSession | null;
4
+ //# sourceMappingURL=rotation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rotation.d.ts","sourceRoot":"","sources":["../../src/advanced/rotation.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE3E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,cAAc,CAAC,mBAAmB,CAAC,GAC3C,mBAAmB,GAAG,IAAI,CAiF5B"}
@@ -0,0 +1,86 @@
1
+ import { Matrix } from '@rasamjs/core';
2
+ import { Guards } from '@rasamjs/core/advanced';
3
+ import { assertPoint } from '../snapping/options.js';
4
+ import { targetNodes, validateNodes } from '../snapping/targets.js';
5
+ import { add, subtract, angleDifference } from './geometry.js';
6
+ export function createRotationSession(context) {
7
+ const { request } = context;
8
+ validateNodes(request.nodes, context.drawing);
9
+ assertPoint(request.client);
10
+ assertPoint(request.pivot);
11
+ if (request.corners.length !== 4)
12
+ throw new TypeError('Rotation needs four corners');
13
+ const root = context.drawing.worldMatrix().inverse();
14
+ const pivot = context.drawing.fromClient(request.pivot), start = context.drawing.fromClient(request.client);
15
+ const corners = request.corners.map((p) => context.drawing.fromClient(p));
16
+ if (!root || !pivot || !start || corners.some((p) => !p))
17
+ return null;
18
+ const parent = request.nodes.length === 1 && Guards.isTransformable(request.nodes[0].parent)
19
+ ? root.multiply(request.nodes[0].parent.worldMatrix())
20
+ : Matrix.identity();
21
+ const inverse = parent.inverse();
22
+ if (!inverse)
23
+ return null;
24
+ const vector = (p) => subtract(inverse.apply(add(p, pivot)), inverse.apply(pivot));
25
+ const first = vector(subtract(corners[1], corners[0]));
26
+ const startAngle = Math.atan2(vector(subtract(start, pivot)).y, vector(subtract(start, pivot)).x);
27
+ const frameAngle = Math.atan2(first.y, first.x);
28
+ const angles = targetNodes(context.drawing, request.nodes, context.options).flatMap((node) => {
29
+ if (!Guards.isTransformable(node))
30
+ return [];
31
+ const matrix = root.multiply(node.worldMatrix());
32
+ return [Math.atan2(matrix.b, matrix.a)];
33
+ });
34
+ if (!angles.length || !context.isCurrent() || !context.isEnabled())
35
+ return null;
36
+ let ended = false, snapped = false;
37
+ const session = {
38
+ get angleSnapped() {
39
+ return snapped;
40
+ },
41
+ snap: (client, modifiers) => {
42
+ assertPoint(client);
43
+ snapped = false;
44
+ if (ended ||
45
+ !context.isEnabled() ||
46
+ (modifiers.ignore && context.options.modifier !== 'none'))
47
+ return client;
48
+ const current = context.drawing.fromClient(client);
49
+ if (!current)
50
+ return client;
51
+ const local = vector(subtract(current, pivot));
52
+ const proposed = frameAngle + Math.atan2(local.y, local.x) - startAngle;
53
+ const mapped = subtract(parent.apply({ x: Math.cos(proposed), y: Math.sin(proposed) }), parent.apply({ x: 0, y: 0 }));
54
+ const visual = Math.atan2(mapped.y, mapped.x);
55
+ let best = null, distance = ((context.advanced.angleTolerance ?? 5) * Math.PI) / 180;
56
+ for (const target of angles) {
57
+ const next = Math.abs(angleDifference(target, visual, Math.PI));
58
+ if (next <= distance) {
59
+ distance = next;
60
+ best = target;
61
+ }
62
+ }
63
+ if (best === null)
64
+ return client;
65
+ const target = vector({ x: Math.cos(best), y: Math.sin(best) });
66
+ const correction = angleDifference(Math.atan2(target.y, target.x), proposed, Math.PI);
67
+ const rotated = {
68
+ x: local.x * Math.cos(correction) - local.y * Math.sin(correction),
69
+ y: local.x * Math.sin(correction) + local.y * Math.cos(correction),
70
+ };
71
+ const offset = subtract(parent.apply(rotated), parent.apply({ x: 0, y: 0 }));
72
+ const result = context.drawing.toClient(add(pivot, offset));
73
+ snapped = !!result;
74
+ return result ?? client;
75
+ },
76
+ end: () => {
77
+ if (!ended) {
78
+ ended = true;
79
+ snapped = false;
80
+ context.onEnd();
81
+ }
82
+ },
83
+ };
84
+ return session;
85
+ }
86
+ //# sourceMappingURL=rotation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rotation.js","sourceRoot":"","sources":["../../src/advanced/rotation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { SnapSession } from '../snapping/types.js';
2
+ import type { AdvancedSnapRequest } from './types.js';
3
+ import type { SessionContext } from './context.js';
4
+ export declare function createAdvancedSession(context: SessionContext<AdvancedSnapRequest>): SnapSession | null;
5
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/advanced/session.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAWnD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,cAAc,CAAC,mBAAmB,CAAC,GAC3C,WAAW,GAAG,IAAI,CAuIpB"}