@weasel-js/core 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +381 -0
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/DrawCommand-Dl0bXNfS.d.ts +500 -0
- package/dist/chunk-775XXAHR.js +216 -0
- package/dist/chunk-775XXAHR.js.map +1 -0
- package/dist/chunk-7V6JEOXE.js +27754 -0
- package/dist/chunk-7V6JEOXE.js.map +1 -0
- package/dist/chunk-AM6ARSPN.js +517 -0
- package/dist/chunk-AM6ARSPN.js.map +1 -0
- package/dist/chunk-BGGZ4CVF.js +248 -0
- package/dist/chunk-BGGZ4CVF.js.map +1 -0
- package/dist/chunk-BHVYVFGV.js +29 -0
- package/dist/chunk-BHVYVFGV.js.map +1 -0
- package/dist/chunk-CQNKCG34.js +831 -0
- package/dist/chunk-CQNKCG34.js.map +1 -0
- package/dist/chunk-GVCNT7UH.js +47 -0
- package/dist/chunk-GVCNT7UH.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +9 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-UGFFCMQP.js +28 -0
- package/dist/chunk-UGFFCMQP.js.map +1 -0
- package/dist/chunk-VOVKONXA.js +103 -0
- package/dist/chunk-VOVKONXA.js.map +1 -0
- package/dist/chunk-Y52N27PF.js +39 -0
- package/dist/chunk-Y52N27PF.js.map +1 -0
- package/dist/clipboard.d.ts +91 -0
- package/dist/clipboard.js +6 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/clone.d.ts +8 -0
- package/dist/clone.js +6 -0
- package/dist/clone.js.map +1 -0
- package/dist/fitViewToBounds-evGsnR8Q.d.ts +62 -0
- package/dist/grid-Cf87knjU.d.ts +153 -0
- package/dist/index-DZBYMsHI.d.ts +1555 -0
- package/dist/index.css +121 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +10200 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/insert.d.ts +33 -0
- package/dist/insert.js +81 -0
- package/dist/insert.js.map +1 -0
- package/dist/move.d.ts +69 -0
- package/dist/move.js +145 -0
- package/dist/move.js.map +1 -0
- package/dist/options-BPPBWMa7.d.ts +64 -0
- package/dist/patterns-builtin.d.ts +55 -0
- package/dist/patterns-builtin.js +100 -0
- package/dist/patterns-builtin.js.map +1 -0
- package/dist/pointSnapToGrid-D7s7QmOF.d.ts +185 -0
- package/dist/registerFont-CP-wCsrz.d.ts +109 -0
- package/dist/registerTexture-BzHTLhD9.d.ts +25 -0
- package/dist/renderer.css +121 -0
- package/dist/renderer.css.map +1 -0
- package/dist/renderer.d.ts +376 -0
- package/dist/renderer.js +13 -0
- package/dist/renderer.js.map +1 -0
- package/dist/resize.d.ts +78 -0
- package/dist/resize.js +5 -0
- package/dist/resize.js.map +1 -0
- package/dist/routing.css +35 -0
- package/dist/routing.css.map +1 -0
- package/dist/routing.d.ts +14 -0
- package/dist/routing.js +4 -0
- package/dist/routing.js.map +1 -0
- package/dist/types-B6MMiodD.d.ts +59 -0
- package/dist/types-BJ8_cyT7.d.ts +130 -0
- package/dist/types-B_-khFM0.d.ts +331 -0
- package/dist/types-BjUi2vA-.d.ts +355 -0
- package/dist/types-Cpb4hii1.d.ts +445 -0
- package/dist/types-D2tTKEU0.d.ts +18 -0
- package/dist/view-DSQgxBJB.d.ts +63 -0
- package/package.json +96 -0
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
import { elevateQuadraticToCubic, cubicBounds, transformCoords, boxToBox } from '@weasel-js/geom';
|
|
2
|
+
|
|
3
|
+
// src/core/units.ts
|
|
4
|
+
function resolveUnit(v, unitSystem) {
|
|
5
|
+
if (typeof v === "number") return v;
|
|
6
|
+
if (!unitSystem) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
`resolveUnit: tagged value { value: ${v.value}, unit: '${v.unit}' } requires a UnitSystem`
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
const factor = unitSystem.units[v.unit];
|
|
12
|
+
if (factor === void 0) {
|
|
13
|
+
const known = Object.keys(unitSystem.units).join(", ") || "(none)";
|
|
14
|
+
throw new Error(
|
|
15
|
+
`resolveUnit: unknown unit '${v.unit}' (system base: '${unitSystem.base}', known units: ${known})`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return v.value * factor;
|
|
19
|
+
}
|
|
20
|
+
function formatUnit(baseValue, displayUnit, unitSystem, opts) {
|
|
21
|
+
const factor = unitSystem.units[displayUnit];
|
|
22
|
+
if (factor === void 0) {
|
|
23
|
+
const known = Object.keys(unitSystem.units).join(", ") || "(none)";
|
|
24
|
+
throw new Error(
|
|
25
|
+
`formatUnit: unknown unit '${displayUnit}' (system base: '${unitSystem.base}', known units: ${known})`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
const precision = opts?.precision ?? 2;
|
|
29
|
+
const suffix = opts?.suffix ?? true;
|
|
30
|
+
const display = baseValue / factor;
|
|
31
|
+
let s = display.toFixed(precision);
|
|
32
|
+
if (s.includes(".")) {
|
|
33
|
+
s = s.replace(/0+$/, "").replace(/\.$/, "");
|
|
34
|
+
}
|
|
35
|
+
return suffix ? `${s}${displayUnit}` : s;
|
|
36
|
+
}
|
|
37
|
+
var IMPERIAL_INCHES = {
|
|
38
|
+
base: "in",
|
|
39
|
+
units: { in: 1, ft: 12, yd: 36, mi: 63360 }
|
|
40
|
+
};
|
|
41
|
+
var METRIC_MM = {
|
|
42
|
+
base: "mm",
|
|
43
|
+
units: { mm: 1, cm: 10, m: 1e3, km: 1e6 }
|
|
44
|
+
};
|
|
45
|
+
var PIXELS = {
|
|
46
|
+
base: "px",
|
|
47
|
+
units: { px: 1 }
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/features/paths/types.ts
|
|
51
|
+
var PATH_M = 0;
|
|
52
|
+
var PATH_L = 1;
|
|
53
|
+
var PATH_C = 2;
|
|
54
|
+
var PATH_Q = 3;
|
|
55
|
+
var PATH_Z = 4;
|
|
56
|
+
var PATH_CMD_LENGTHS = [2, 2, 6, 4, 0];
|
|
57
|
+
function boundsOfPath(path) {
|
|
58
|
+
if (path.kind === "rect") return path;
|
|
59
|
+
const { commands, coords } = path;
|
|
60
|
+
if (commands.length === 0) {
|
|
61
|
+
return { kind: "rect", x: 0, y: 0, width: 0, height: 0 };
|
|
62
|
+
}
|
|
63
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
64
|
+
let ci = 0;
|
|
65
|
+
let seen = false;
|
|
66
|
+
let px = 0, py = 0;
|
|
67
|
+
const include = (x, y) => {
|
|
68
|
+
if (x < minX) minX = x;
|
|
69
|
+
if (x > maxX) maxX = x;
|
|
70
|
+
if (y < minY) minY = y;
|
|
71
|
+
if (y > maxY) maxY = y;
|
|
72
|
+
seen = true;
|
|
73
|
+
};
|
|
74
|
+
for (let i = 0; i < commands.length; i++) {
|
|
75
|
+
const cmd = commands[i];
|
|
76
|
+
switch (cmd) {
|
|
77
|
+
case PATH_M:
|
|
78
|
+
case PATH_L: {
|
|
79
|
+
const x = coords[ci], y = coords[ci + 1];
|
|
80
|
+
include(x, y);
|
|
81
|
+
px = x;
|
|
82
|
+
py = y;
|
|
83
|
+
ci += 2;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case PATH_C: {
|
|
87
|
+
const x1 = coords[ci], y1 = coords[ci + 1];
|
|
88
|
+
const x2 = coords[ci + 2], y2 = coords[ci + 3];
|
|
89
|
+
const x3 = coords[ci + 4], y3 = coords[ci + 5];
|
|
90
|
+
const [bMinX, bMinY, bMaxX, bMaxY] = cubicBounds(px, py, x1, y1, x2, y2, x3, y3);
|
|
91
|
+
include(bMinX, bMinY);
|
|
92
|
+
include(bMaxX, bMaxY);
|
|
93
|
+
px = x3;
|
|
94
|
+
py = y3;
|
|
95
|
+
ci += 6;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case PATH_Q: {
|
|
99
|
+
const qx1 = coords[ci], qy1 = coords[ci + 1];
|
|
100
|
+
const qx2 = coords[ci + 2], qy2 = coords[ci + 3];
|
|
101
|
+
const [c1x, c1y, c2x, c2y] = elevateQuadraticToCubic(px, py, qx1, qy1, qx2, qy2);
|
|
102
|
+
const [bMinX, bMinY, bMaxX, bMaxY] = cubicBounds(px, py, c1x, c1y, c2x, c2y, qx2, qy2);
|
|
103
|
+
include(bMinX, bMinY);
|
|
104
|
+
include(bMaxX, bMaxY);
|
|
105
|
+
px = qx2;
|
|
106
|
+
py = qy2;
|
|
107
|
+
ci += 4;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case PATH_Z:
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
throw new Error(`boundsOfPath: unknown command ${cmd}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!seen) return { kind: "rect", x: 0, y: 0, width: 0, height: 0 };
|
|
117
|
+
return { kind: "rect", x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/features/paths/flatten.ts
|
|
121
|
+
var DEFAULT_FLATTEN_TOLERANCE = 0.5;
|
|
122
|
+
function distPointToLine(px, py, ax, ay, bx, by) {
|
|
123
|
+
const dx = bx - ax;
|
|
124
|
+
const dy = by - ay;
|
|
125
|
+
const len2 = dx * dx + dy * dy;
|
|
126
|
+
if (len2 === 0) {
|
|
127
|
+
const ex = px - ax;
|
|
128
|
+
const ey = py - ay;
|
|
129
|
+
return Math.sqrt(ex * ex + ey * ey);
|
|
130
|
+
}
|
|
131
|
+
const cross = (px - ax) * dy - (py - ay) * dx;
|
|
132
|
+
return Math.abs(cross) / Math.sqrt(len2);
|
|
133
|
+
}
|
|
134
|
+
function flattenCubic(x0, y0, x1, y1, x2, y2, x3, y3, tolerance, out) {
|
|
135
|
+
const d1 = distPointToLine(x1, y1, x0, y0, x3, y3);
|
|
136
|
+
const d2 = distPointToLine(x2, y2, x0, y0, x3, y3);
|
|
137
|
+
if (Math.max(d1, d2) <= tolerance) {
|
|
138
|
+
out.push(x3, y3);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const x01 = (x0 + x1) * 0.5, y01 = (y0 + y1) * 0.5;
|
|
142
|
+
const x12 = (x1 + x2) * 0.5, y12 = (y1 + y2) * 0.5;
|
|
143
|
+
const x23 = (x2 + x3) * 0.5, y23 = (y2 + y3) * 0.5;
|
|
144
|
+
const x012 = (x01 + x12) * 0.5, y012 = (y01 + y12) * 0.5;
|
|
145
|
+
const x123 = (x12 + x23) * 0.5, y123 = (y12 + y23) * 0.5;
|
|
146
|
+
const x0123 = (x012 + x123) * 0.5, y0123 = (y012 + y123) * 0.5;
|
|
147
|
+
flattenCubic(x0, y0, x01, y01, x012, y012, x0123, y0123, tolerance, out);
|
|
148
|
+
flattenCubic(x0123, y0123, x123, y123, x23, y23, x3, y3, tolerance, out);
|
|
149
|
+
}
|
|
150
|
+
function flattenQuadratic(x0, y0, x1, y1, x2, y2, tolerance, out) {
|
|
151
|
+
const d = distPointToLine(x1, y1, x0, y0, x2, y2);
|
|
152
|
+
if (d <= tolerance) {
|
|
153
|
+
out.push(x2, y2);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const x01 = (x0 + x1) * 0.5, y01 = (y0 + y1) * 0.5;
|
|
157
|
+
const x12 = (x1 + x2) * 0.5, y12 = (y1 + y2) * 0.5;
|
|
158
|
+
const x012 = (x01 + x12) * 0.5, y012 = (y01 + y12) * 0.5;
|
|
159
|
+
flattenQuadratic(x0, y0, x01, y01, x012, y012, tolerance, out);
|
|
160
|
+
flattenQuadratic(x012, y012, x12, y12, x2, y2, tolerance, out);
|
|
161
|
+
}
|
|
162
|
+
function flattenCubicWithArcLen(x0, y0, x1, y1, x2, y2, x3, y3, tolerance, out, arcOut) {
|
|
163
|
+
return flattenCubicArcRec(x0, y0, x1, y1, x2, y2, x3, y3, tolerance, out, arcOut, 0);
|
|
164
|
+
}
|
|
165
|
+
function flattenCubicArcRec(x0, y0, x1, y1, x2, y2, x3, y3, tolerance, out, arcOut, accum) {
|
|
166
|
+
const d1 = distPointToLine(x1, y1, x0, y0, x3, y3);
|
|
167
|
+
const d2 = distPointToLine(x2, y2, x0, y0, x3, y3);
|
|
168
|
+
if (Math.max(d1, d2) <= tolerance) {
|
|
169
|
+
const lastX = out.length >= 2 ? out[out.length - 2] : x0;
|
|
170
|
+
const lastY = out.length >= 2 ? out[out.length - 1] : y0;
|
|
171
|
+
const seg = Math.hypot(x3 - lastX, y3 - lastY);
|
|
172
|
+
accum += seg;
|
|
173
|
+
out.push(x3, y3);
|
|
174
|
+
arcOut.push(accum);
|
|
175
|
+
return accum;
|
|
176
|
+
}
|
|
177
|
+
const x01 = (x0 + x1) * 0.5, y01 = (y0 + y1) * 0.5;
|
|
178
|
+
const x12 = (x1 + x2) * 0.5, y12 = (y1 + y2) * 0.5;
|
|
179
|
+
const x23 = (x2 + x3) * 0.5, y23 = (y2 + y3) * 0.5;
|
|
180
|
+
const x012 = (x01 + x12) * 0.5, y012 = (y01 + y12) * 0.5;
|
|
181
|
+
const x123 = (x12 + x23) * 0.5, y123 = (y12 + y23) * 0.5;
|
|
182
|
+
const x0123 = (x012 + x123) * 0.5, y0123 = (y012 + y123) * 0.5;
|
|
183
|
+
accum = flattenCubicArcRec(x0, y0, x01, y01, x012, y012, x0123, y0123, tolerance, out, arcOut, accum);
|
|
184
|
+
accum = flattenCubicArcRec(x0123, y0123, x123, y123, x23, y23, x3, y3, tolerance, out, arcOut, accum);
|
|
185
|
+
return accum;
|
|
186
|
+
}
|
|
187
|
+
function flattenQuadraticWithArcLen(x0, y0, x1, y1, x2, y2, tolerance, out, arcOut) {
|
|
188
|
+
return flattenQuadraticArcRec(x0, y0, x1, y1, x2, y2, tolerance, out, arcOut, 0);
|
|
189
|
+
}
|
|
190
|
+
function flattenQuadraticArcRec(x0, y0, x1, y1, x2, y2, tolerance, out, arcOut, accum) {
|
|
191
|
+
const d = distPointToLine(x1, y1, x0, y0, x2, y2);
|
|
192
|
+
if (d <= tolerance) {
|
|
193
|
+
const lastX = out.length >= 2 ? out[out.length - 2] : x0;
|
|
194
|
+
const lastY = out.length >= 2 ? out[out.length - 1] : y0;
|
|
195
|
+
const seg = Math.hypot(x2 - lastX, y2 - lastY);
|
|
196
|
+
accum += seg;
|
|
197
|
+
out.push(x2, y2);
|
|
198
|
+
arcOut.push(accum);
|
|
199
|
+
return accum;
|
|
200
|
+
}
|
|
201
|
+
const x01 = (x0 + x1) * 0.5, y01 = (y0 + y1) * 0.5;
|
|
202
|
+
const x12 = (x1 + x2) * 0.5, y12 = (y1 + y2) * 0.5;
|
|
203
|
+
const x012 = (x01 + x12) * 0.5, y012 = (y01 + y12) * 0.5;
|
|
204
|
+
accum = flattenQuadraticArcRec(x0, y0, x01, y01, x012, y012, tolerance, out, arcOut, accum);
|
|
205
|
+
accum = flattenQuadraticArcRec(x012, y012, x12, y12, x2, y2, tolerance, out, arcOut, accum);
|
|
206
|
+
return accum;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/features/paths/hitTest.ts
|
|
210
|
+
function pointInPath(path, x, y, opts = {}) {
|
|
211
|
+
if (path.kind === "rect") {
|
|
212
|
+
return x >= path.x && x <= path.x + path.width && y >= path.y && y <= path.y + path.height;
|
|
213
|
+
}
|
|
214
|
+
return pointInPolygonPath(path, x, y, opts.tolerance ?? DEFAULT_FLATTEN_TOLERANCE);
|
|
215
|
+
}
|
|
216
|
+
function pointInPolygonPath(path, x, y, tolerance) {
|
|
217
|
+
const { commands, coords, fillRule } = path;
|
|
218
|
+
if (commands.length === 0) return false;
|
|
219
|
+
let crossings = 0;
|
|
220
|
+
let winding = 0;
|
|
221
|
+
let subStartX = 0, subStartY = 0;
|
|
222
|
+
let curX = 0, curY = 0;
|
|
223
|
+
let subVerts = [];
|
|
224
|
+
let subOpen = false;
|
|
225
|
+
const flushSubpath = (closed) => {
|
|
226
|
+
if (closed && subVerts.length >= 4) {
|
|
227
|
+
subVerts.push(subStartX, subStartY);
|
|
228
|
+
for (let i = 0; i < subVerts.length - 2; i += 2) {
|
|
229
|
+
const ax = subVerts[i], ay = subVerts[i + 1];
|
|
230
|
+
const bx = subVerts[i + 2], by = subVerts[i + 3];
|
|
231
|
+
if (segmentCrossesRayRight(x, y, ax, ay, bx, by)) {
|
|
232
|
+
crossings++;
|
|
233
|
+
if (ay <= y && by > y) winding++;
|
|
234
|
+
else if (by <= y && ay > y) winding--;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
subVerts = [];
|
|
239
|
+
subOpen = false;
|
|
240
|
+
};
|
|
241
|
+
let ci = 0;
|
|
242
|
+
for (let i = 0; i < commands.length; i++) {
|
|
243
|
+
const cmd = commands[i];
|
|
244
|
+
switch (cmd) {
|
|
245
|
+
case PATH_M: {
|
|
246
|
+
if (subOpen) flushSubpath(false);
|
|
247
|
+
subStartX = coords[ci];
|
|
248
|
+
subStartY = coords[ci + 1];
|
|
249
|
+
curX = subStartX;
|
|
250
|
+
curY = subStartY;
|
|
251
|
+
subVerts.push(curX, curY);
|
|
252
|
+
subOpen = true;
|
|
253
|
+
ci += 2;
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case PATH_L: {
|
|
257
|
+
curX = coords[ci];
|
|
258
|
+
curY = coords[ci + 1];
|
|
259
|
+
subVerts.push(curX, curY);
|
|
260
|
+
ci += 2;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case PATH_C: {
|
|
264
|
+
const x1 = coords[ci], y1 = coords[ci + 1];
|
|
265
|
+
const x2 = coords[ci + 2], y2 = coords[ci + 3];
|
|
266
|
+
const x3 = coords[ci + 4], y3 = coords[ci + 5];
|
|
267
|
+
flattenCubic(curX, curY, x1, y1, x2, y2, x3, y3, tolerance, subVerts);
|
|
268
|
+
curX = x3;
|
|
269
|
+
curY = y3;
|
|
270
|
+
ci += 6;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case PATH_Q: {
|
|
274
|
+
const x1 = coords[ci], y1 = coords[ci + 1];
|
|
275
|
+
const x2 = coords[ci + 2], y2 = coords[ci + 3];
|
|
276
|
+
flattenQuadratic(curX, curY, x1, y1, x2, y2, tolerance, subVerts);
|
|
277
|
+
curX = x2;
|
|
278
|
+
curY = y2;
|
|
279
|
+
ci += 4;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case PATH_Z: {
|
|
283
|
+
flushSubpath(true);
|
|
284
|
+
curX = subStartX;
|
|
285
|
+
curY = subStartY;
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
default:
|
|
289
|
+
throw new Error(`pointInPath: unknown command ${cmd}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (subOpen) flushSubpath(false);
|
|
293
|
+
return fillRule === "evenodd" ? (crossings & 1) === 1 : winding !== 0;
|
|
294
|
+
}
|
|
295
|
+
function segmentCrossesRayRight(x, y, ax, ay, bx, by) {
|
|
296
|
+
if (ay > y === by > y) return false;
|
|
297
|
+
const xCross = ax + (y - ay) / (by - ay) * (bx - ax);
|
|
298
|
+
return x < xCross;
|
|
299
|
+
}
|
|
300
|
+
function strokeHitTest(path, x, y, threshold, opts = {}) {
|
|
301
|
+
const t2 = threshold * threshold;
|
|
302
|
+
if (path.kind === "rect") {
|
|
303
|
+
const { x: rx, y: ry, width: w, height: h } = path;
|
|
304
|
+
return pointSegmentDist2(x, y, rx, ry, rx + w, ry) <= t2 || pointSegmentDist2(x, y, rx + w, ry, rx + w, ry + h) <= t2 || pointSegmentDist2(x, y, rx + w, ry + h, rx, ry + h) <= t2 || pointSegmentDist2(x, y, rx, ry + h, rx, ry) <= t2;
|
|
305
|
+
}
|
|
306
|
+
return polylineWithinThreshold(path, x, y, t2, opts.tolerance ?? DEFAULT_FLATTEN_TOLERANCE);
|
|
307
|
+
}
|
|
308
|
+
function polylineWithinThreshold(path, px, py, t2, tolerance) {
|
|
309
|
+
const { commands, coords } = path;
|
|
310
|
+
if (commands.length === 0) return false;
|
|
311
|
+
let subStartX = 0, subStartY = 0;
|
|
312
|
+
let curX = 0, curY = 0;
|
|
313
|
+
let subVerts = [];
|
|
314
|
+
let subOpen = false;
|
|
315
|
+
const checkSegments = (closed) => {
|
|
316
|
+
if (closed && subVerts.length >= 4) subVerts.push(subStartX, subStartY);
|
|
317
|
+
for (let i = 0; i < subVerts.length - 2; i += 2) {
|
|
318
|
+
if (pointSegmentDist2(px, py, subVerts[i], subVerts[i + 1], subVerts[i + 2], subVerts[i + 3]) <= t2) {
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return false;
|
|
323
|
+
};
|
|
324
|
+
let ci = 0;
|
|
325
|
+
for (let i = 0; i < commands.length; i++) {
|
|
326
|
+
const cmd = commands[i];
|
|
327
|
+
switch (cmd) {
|
|
328
|
+
case PATH_M: {
|
|
329
|
+
if (subOpen) {
|
|
330
|
+
if (checkSegments(false)) return true;
|
|
331
|
+
subVerts = [];
|
|
332
|
+
}
|
|
333
|
+
subStartX = coords[ci];
|
|
334
|
+
subStartY = coords[ci + 1];
|
|
335
|
+
curX = subStartX;
|
|
336
|
+
curY = subStartY;
|
|
337
|
+
subVerts.push(curX, curY);
|
|
338
|
+
subOpen = true;
|
|
339
|
+
ci += 2;
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
case PATH_L: {
|
|
343
|
+
curX = coords[ci];
|
|
344
|
+
curY = coords[ci + 1];
|
|
345
|
+
subVerts.push(curX, curY);
|
|
346
|
+
ci += 2;
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
case PATH_C: {
|
|
350
|
+
const x1 = coords[ci], y1 = coords[ci + 1];
|
|
351
|
+
const x2 = coords[ci + 2], y2 = coords[ci + 3];
|
|
352
|
+
const x3 = coords[ci + 4], y3 = coords[ci + 5];
|
|
353
|
+
flattenCubic(curX, curY, x1, y1, x2, y2, x3, y3, tolerance, subVerts);
|
|
354
|
+
curX = x3;
|
|
355
|
+
curY = y3;
|
|
356
|
+
ci += 6;
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case PATH_Q: {
|
|
360
|
+
const x1 = coords[ci], y1 = coords[ci + 1];
|
|
361
|
+
const x2 = coords[ci + 2], y2 = coords[ci + 3];
|
|
362
|
+
flattenQuadratic(curX, curY, x1, y1, x2, y2, tolerance, subVerts);
|
|
363
|
+
curX = x2;
|
|
364
|
+
curY = y2;
|
|
365
|
+
ci += 4;
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
case PATH_Z: {
|
|
369
|
+
if (checkSegments(true)) return true;
|
|
370
|
+
subVerts = [];
|
|
371
|
+
subOpen = false;
|
|
372
|
+
curX = subStartX;
|
|
373
|
+
curY = subStartY;
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
default:
|
|
377
|
+
throw new Error(`strokeHitTest: unknown command ${cmd}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (subOpen && checkSegments(false)) return true;
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
function pointSegmentDist2(px, py, ax, ay, bx, by) {
|
|
384
|
+
const dx = bx - ax;
|
|
385
|
+
const dy = by - ay;
|
|
386
|
+
const len2 = dx * dx + dy * dy;
|
|
387
|
+
if (len2 === 0) {
|
|
388
|
+
const ex2 = px - ax, ey2 = py - ay;
|
|
389
|
+
return ex2 * ex2 + ey2 * ey2;
|
|
390
|
+
}
|
|
391
|
+
let t = ((px - ax) * dx + (py - ay) * dy) / len2;
|
|
392
|
+
if (t < 0) t = 0;
|
|
393
|
+
else if (t > 1) t = 1;
|
|
394
|
+
const cx = ax + t * dx;
|
|
395
|
+
const cy = ay + t * dy;
|
|
396
|
+
const ex = px - cx, ey = py - cy;
|
|
397
|
+
return ex * ex + ey * ey;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/features/paths/builder.ts
|
|
401
|
+
var PathBuilder = class _PathBuilder {
|
|
402
|
+
cmds = [];
|
|
403
|
+
xs = [];
|
|
404
|
+
fillRule = "nonzero";
|
|
405
|
+
/** Seed a builder with an existing `PolygonPath`. Useful for extending
|
|
406
|
+
* an in-flight path with another segment (e.g. appending a cubic to
|
|
407
|
+
* the end of a curve in response to a user action) without hand-
|
|
408
|
+
* reaching into the `commands` / `coords` typed arrays.
|
|
409
|
+
*
|
|
410
|
+
* ```ts
|
|
411
|
+
* const next = PathBuilder.fromPath(current).curveTo(...).build();
|
|
412
|
+
* ```
|
|
413
|
+
*/
|
|
414
|
+
static fromPath(path) {
|
|
415
|
+
const b = new _PathBuilder();
|
|
416
|
+
b.cmds = Array.from(path.commands);
|
|
417
|
+
b.xs = Array.from(path.coords);
|
|
418
|
+
b.fillRule = path.fillRule;
|
|
419
|
+
return b;
|
|
420
|
+
}
|
|
421
|
+
setFillRule(rule) {
|
|
422
|
+
this.fillRule = rule;
|
|
423
|
+
return this;
|
|
424
|
+
}
|
|
425
|
+
moveTo(x, y) {
|
|
426
|
+
this.cmds.push(PATH_M);
|
|
427
|
+
this.xs.push(x, y);
|
|
428
|
+
return this;
|
|
429
|
+
}
|
|
430
|
+
lineTo(x, y) {
|
|
431
|
+
this.cmds.push(PATH_L);
|
|
432
|
+
this.xs.push(x, y);
|
|
433
|
+
return this;
|
|
434
|
+
}
|
|
435
|
+
/** Cubic bezier to (x, y) with control points (x1, y1) and (x2, y2). */
|
|
436
|
+
curveTo(x1, y1, x2, y2, x, y) {
|
|
437
|
+
this.cmds.push(PATH_C);
|
|
438
|
+
this.xs.push(x1, y1, x2, y2, x, y);
|
|
439
|
+
return this;
|
|
440
|
+
}
|
|
441
|
+
/** Quadratic bezier to (x, y) with control point (x1, y1). */
|
|
442
|
+
quadTo(x1, y1, x, y) {
|
|
443
|
+
this.cmds.push(PATH_Q);
|
|
444
|
+
this.xs.push(x1, y1, x, y);
|
|
445
|
+
return this;
|
|
446
|
+
}
|
|
447
|
+
close() {
|
|
448
|
+
this.cmds.push(PATH_Z);
|
|
449
|
+
return this;
|
|
450
|
+
}
|
|
451
|
+
build() {
|
|
452
|
+
return {
|
|
453
|
+
kind: "polygon",
|
|
454
|
+
commands: new Uint8Array(this.cmds),
|
|
455
|
+
coords: new Float32Array(this.xs),
|
|
456
|
+
fillRule: this.fillRule
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
function rectPath(x, y, width, height) {
|
|
461
|
+
return { kind: "rect", x, y, width, height };
|
|
462
|
+
}
|
|
463
|
+
function polygonFromPoints(points, opts = {}) {
|
|
464
|
+
if (points.length === 0) {
|
|
465
|
+
return {
|
|
466
|
+
kind: "polygon",
|
|
467
|
+
commands: new Uint8Array(),
|
|
468
|
+
coords: new Float32Array(),
|
|
469
|
+
fillRule: opts.fillRule ?? "nonzero"
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
const b = new PathBuilder();
|
|
473
|
+
if (opts.fillRule) b.setFillRule(opts.fillRule);
|
|
474
|
+
b.moveTo(points[0].x, points[0].y);
|
|
475
|
+
for (let i = 1; i < points.length; i++) b.lineTo(points[i].x, points[i].y);
|
|
476
|
+
b.close();
|
|
477
|
+
return b.build();
|
|
478
|
+
}
|
|
479
|
+
var ELLIPSE_KAPPA = 0.5522847498307936;
|
|
480
|
+
function ellipsePath(bounds) {
|
|
481
|
+
const cx = bounds.x + bounds.width / 2;
|
|
482
|
+
const cy = bounds.y + bounds.height / 2;
|
|
483
|
+
const rx = bounds.width / 2;
|
|
484
|
+
const ry = bounds.height / 2;
|
|
485
|
+
const ox = rx * ELLIPSE_KAPPA;
|
|
486
|
+
const oy = ry * ELLIPSE_KAPPA;
|
|
487
|
+
const b = new PathBuilder();
|
|
488
|
+
b.moveTo(cx + rx, cy);
|
|
489
|
+
b.curveTo(cx + rx, cy + oy, cx + ox, cy + ry, cx, cy + ry);
|
|
490
|
+
b.curveTo(cx - ox, cy + ry, cx - rx, cy + oy, cx - rx, cy);
|
|
491
|
+
b.curveTo(cx - rx, cy - oy, cx - ox, cy - ry, cx, cy - ry);
|
|
492
|
+
b.curveTo(cx + ox, cy - ry, cx + rx, cy - oy, cx + rx, cy);
|
|
493
|
+
b.close();
|
|
494
|
+
return b.build();
|
|
495
|
+
}
|
|
496
|
+
function regularPolygonPath(center, radius, sides, rotation = 0) {
|
|
497
|
+
const b = new PathBuilder();
|
|
498
|
+
for (let i = 0; i < sides; i++) {
|
|
499
|
+
const a = rotation + i / sides * Math.PI * 2;
|
|
500
|
+
const x = center.x + radius * Math.cos(a);
|
|
501
|
+
const y = center.y + radius * Math.sin(a);
|
|
502
|
+
if (i === 0) b.moveTo(x, y);
|
|
503
|
+
else b.lineTo(x, y);
|
|
504
|
+
}
|
|
505
|
+
b.close();
|
|
506
|
+
return b.build();
|
|
507
|
+
}
|
|
508
|
+
function starPath(center, outerRadius, points = 5, innerRadius = outerRadius / 2, rotation = 0) {
|
|
509
|
+
const b = new PathBuilder();
|
|
510
|
+
const n = points * 2;
|
|
511
|
+
for (let i = 0; i < n; i++) {
|
|
512
|
+
const a = rotation + i / n * Math.PI * 2;
|
|
513
|
+
const r = i % 2 === 0 ? outerRadius : innerRadius;
|
|
514
|
+
const x = center.x + r * Math.cos(a);
|
|
515
|
+
const y = center.y + r * Math.sin(a);
|
|
516
|
+
if (i === 0) b.moveTo(x, y);
|
|
517
|
+
else b.lineTo(x, y);
|
|
518
|
+
}
|
|
519
|
+
b.close();
|
|
520
|
+
return b.build();
|
|
521
|
+
}
|
|
522
|
+
function linePath(a, b) {
|
|
523
|
+
const pb = new PathBuilder();
|
|
524
|
+
pb.moveTo(a.x, a.y);
|
|
525
|
+
pb.lineTo(b.x, b.y);
|
|
526
|
+
return pb.build();
|
|
527
|
+
}
|
|
528
|
+
function transformPath(path, m) {
|
|
529
|
+
const [a, b, c, d, e, f] = m;
|
|
530
|
+
if (path.kind === "rect") {
|
|
531
|
+
if (b === 0 && c === 0) {
|
|
532
|
+
const x0 = a * path.x + e;
|
|
533
|
+
const y0 = d * path.y + f;
|
|
534
|
+
const x1 = a * (path.x + path.width) + e;
|
|
535
|
+
const y1 = d * (path.y + path.height) + f;
|
|
536
|
+
return {
|
|
537
|
+
kind: "rect",
|
|
538
|
+
x: Math.min(x0, x1),
|
|
539
|
+
y: Math.min(y0, y1),
|
|
540
|
+
width: Math.abs(x1 - x0),
|
|
541
|
+
height: Math.abs(y1 - y0)
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
const poly = new PathBuilder().moveTo(path.x, path.y).lineTo(path.x + path.width, path.y).lineTo(path.x + path.width, path.y + path.height).lineTo(path.x, path.y + path.height).close().build();
|
|
545
|
+
return transformPolygon(poly, m);
|
|
546
|
+
}
|
|
547
|
+
return transformPolygon(path, m);
|
|
548
|
+
}
|
|
549
|
+
function transformPolygon(path, m) {
|
|
550
|
+
const mapped = transformCoords(path.coords, m);
|
|
551
|
+
return { ...path, coords: Float32Array.from(mapped) };
|
|
552
|
+
}
|
|
553
|
+
function translatePath(path, dx, dy) {
|
|
554
|
+
if (path.kind === "rect") {
|
|
555
|
+
return { kind: "rect", x: path.x + dx, y: path.y + dy, width: path.width, height: path.height };
|
|
556
|
+
}
|
|
557
|
+
return translatePolygonCopy(path, dx, dy);
|
|
558
|
+
}
|
|
559
|
+
function translatePolygonCopy(path, dx, dy) {
|
|
560
|
+
const next = new Float32Array(path.coords.length);
|
|
561
|
+
const { commands, coords } = path;
|
|
562
|
+
let ci = 0;
|
|
563
|
+
for (let i = 0; i < commands.length; i++) {
|
|
564
|
+
const len = COORD_COUNT[commands[i]];
|
|
565
|
+
for (let k = 0; k < len; k += 2) {
|
|
566
|
+
next[ci + k] = coords[ci + k] + dx;
|
|
567
|
+
next[ci + k + 1] = coords[ci + k + 1] + dy;
|
|
568
|
+
}
|
|
569
|
+
ci += len;
|
|
570
|
+
}
|
|
571
|
+
return { kind: "polygon", commands: path.commands, coords: next, fillRule: path.fillRule };
|
|
572
|
+
}
|
|
573
|
+
function translatePolygonInPlace(path, dx, dy) {
|
|
574
|
+
const { commands, coords } = path;
|
|
575
|
+
let ci = 0;
|
|
576
|
+
for (let i = 0; i < commands.length; i++) {
|
|
577
|
+
const len = COORD_COUNT[commands[i]];
|
|
578
|
+
for (let k = 0; k < len; k += 2) {
|
|
579
|
+
coords[ci + k] += dx;
|
|
580
|
+
coords[ci + k + 1] += dy;
|
|
581
|
+
}
|
|
582
|
+
ci += len;
|
|
583
|
+
}
|
|
584
|
+
return path;
|
|
585
|
+
}
|
|
586
|
+
function scalePathToBounds(path, target) {
|
|
587
|
+
if (path.kind === "rect") {
|
|
588
|
+
return { kind: "rect", x: target.x, y: target.y, width: target.width, height: target.height };
|
|
589
|
+
}
|
|
590
|
+
const src = boundsOfPath(path);
|
|
591
|
+
return scalePolygon(path, src, target);
|
|
592
|
+
}
|
|
593
|
+
function scalePolygon(path, src, dst) {
|
|
594
|
+
const m = boxToBox(src.x, src.y, src.width, src.height, dst.x, dst.y, dst.width, dst.height);
|
|
595
|
+
return transformPath(path, m);
|
|
596
|
+
}
|
|
597
|
+
var COORD_COUNT = {
|
|
598
|
+
[PATH_M]: 2,
|
|
599
|
+
[PATH_L]: 2,
|
|
600
|
+
[PATH_C]: 6,
|
|
601
|
+
[PATH_Q]: 4,
|
|
602
|
+
[PATH_Z]: 0
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// src/interactions/actions/resize/geometry.ts
|
|
606
|
+
function aabbIntersectsRect(b, r) {
|
|
607
|
+
return b.x < r.x + r.width && b.x + b.width > r.x && b.y < r.y + r.height && b.y + b.height > r.y;
|
|
608
|
+
}
|
|
609
|
+
var RECT_POSE_DESCRIPTOR = {
|
|
610
|
+
getBounds: (p) => p,
|
|
611
|
+
remapBounds: (p, src, dst) => {
|
|
612
|
+
const sx = src.width === 0 ? 1 : dst.width / src.width;
|
|
613
|
+
const sy = src.height === 0 ? 1 : dst.height / src.height;
|
|
614
|
+
return {
|
|
615
|
+
...p,
|
|
616
|
+
x: dst.x + (p.x - src.x) * sx,
|
|
617
|
+
y: dst.y + (p.y - src.y) * sy,
|
|
618
|
+
width: p.width * sx,
|
|
619
|
+
height: p.height * sy
|
|
620
|
+
};
|
|
621
|
+
},
|
|
622
|
+
translate: (p, dx, dy) => ({ ...p, x: p.x + dx, y: p.y + dy }),
|
|
623
|
+
intersectsRect: (p, r) => aabbIntersectsRect(p, r),
|
|
624
|
+
lerp: (a, b, t) => ({
|
|
625
|
+
...a,
|
|
626
|
+
x: a.x + (b.x - a.x) * t,
|
|
627
|
+
y: a.y + (b.y - a.y) * t,
|
|
628
|
+
width: a.width + (b.width - a.width) * t,
|
|
629
|
+
height: a.height + (b.height - a.height) * t
|
|
630
|
+
})
|
|
631
|
+
};
|
|
632
|
+
var ROTATED_POSE_DESCRIPTOR = {
|
|
633
|
+
getBounds: RECT_POSE_DESCRIPTOR.getBounds,
|
|
634
|
+
remapBounds: RECT_POSE_DESCRIPTOR.remapBounds,
|
|
635
|
+
translate: RECT_POSE_DESCRIPTOR.translate,
|
|
636
|
+
intersectsRect: RECT_POSE_DESCRIPTOR.intersectsRect,
|
|
637
|
+
lerp: RECT_POSE_DESCRIPTOR.lerp,
|
|
638
|
+
getRotation: (p) => p.rotation
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// src/features/paths/poseDescriptor.ts
|
|
642
|
+
var pathPoseDescriptor = {
|
|
643
|
+
getBounds: (path) => boundsOfPath(path),
|
|
644
|
+
remapBounds: (path, src, dst) => {
|
|
645
|
+
const sx = src.width === 0 ? 0 : dst.width / src.width;
|
|
646
|
+
const sy = src.height === 0 ? 0 : dst.height / src.height;
|
|
647
|
+
if (path.kind === "rect") {
|
|
648
|
+
return {
|
|
649
|
+
kind: "rect",
|
|
650
|
+
x: dst.x + (path.x - src.x) * sx,
|
|
651
|
+
y: dst.y + (path.y - src.y) * sy,
|
|
652
|
+
width: path.width * sx,
|
|
653
|
+
height: path.height * sy
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
return remapPolygon(path, src, dst, sx, sy);
|
|
657
|
+
},
|
|
658
|
+
translate: (path, dx, dy) => translatePath(path, dx, dy),
|
|
659
|
+
// WHY: AABB pre-test is cheap; only fall through to per-corner pointInPath
|
|
660
|
+
// when the rect is fully inside the AABB (silhouette test).
|
|
661
|
+
intersectsRect: (path, rect) => {
|
|
662
|
+
const b = boundsOfPath(path);
|
|
663
|
+
if (!aabbIntersectsRect(b, rect)) return false;
|
|
664
|
+
if (path.kind === "rect") return true;
|
|
665
|
+
if (pointInPath(path, rect.x, rect.y) || pointInPath(path, rect.x + rect.width, rect.y) || pointInPath(path, rect.x, rect.y + rect.height) || pointInPath(path, rect.x + rect.width, rect.y + rect.height)) return true;
|
|
666
|
+
return true;
|
|
667
|
+
},
|
|
668
|
+
lerp: (a, b, t) => {
|
|
669
|
+
if (a.kind === "rect" && b.kind === "rect") {
|
|
670
|
+
return {
|
|
671
|
+
kind: "rect",
|
|
672
|
+
x: a.x + (b.x - a.x) * t,
|
|
673
|
+
y: a.y + (b.y - a.y) * t,
|
|
674
|
+
width: a.width + (b.width - a.width) * t,
|
|
675
|
+
height: a.height + (b.height - a.height) * t
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (a.kind === "polygon" && b.kind === "polygon" && a.coords.length === b.coords.length) {
|
|
679
|
+
const next = new Float32Array(a.coords.length);
|
|
680
|
+
for (let i = 0; i < a.coords.length; i++) {
|
|
681
|
+
next[i] = a.coords[i] + (b.coords[i] - a.coords[i]) * t;
|
|
682
|
+
}
|
|
683
|
+
return { kind: "polygon", commands: a.commands, coords: next, fillRule: a.fillRule };
|
|
684
|
+
}
|
|
685
|
+
throw new Error("pathPoseDescriptor.lerp: incompatible path shapes");
|
|
686
|
+
},
|
|
687
|
+
// Path poses carry no x/y/width/height/rotation fields — the kit's
|
|
688
|
+
// `wrapWithPoseRotation` helper has nothing to bind a rotation to, and
|
|
689
|
+
// `remapBounds` would drop a synthetic rotation field on every commit.
|
|
690
|
+
// Returning false here hides the rotation affordance for Path consumers
|
|
691
|
+
// so the rotate cursor doesn't appear without a working interaction.
|
|
692
|
+
supportsRotation: () => false
|
|
693
|
+
};
|
|
694
|
+
function remapPolygon(path, src, dst, sx, sy) {
|
|
695
|
+
const next = new Float32Array(path.coords.length);
|
|
696
|
+
const { commands, coords } = path;
|
|
697
|
+
let ci = 0;
|
|
698
|
+
for (let i = 0; i < commands.length; i++) {
|
|
699
|
+
const len = COORD_COUNT2[commands[i]];
|
|
700
|
+
for (let k = 0; k < len; k += 2) {
|
|
701
|
+
next[ci + k] = dst.x + (coords[ci + k] - src.x) * sx;
|
|
702
|
+
next[ci + k + 1] = dst.y + (coords[ci + k + 1] - src.y) * sy;
|
|
703
|
+
}
|
|
704
|
+
ci += len;
|
|
705
|
+
}
|
|
706
|
+
return { kind: "polygon", commands: path.commands, coords: next, fillRule: path.fillRule };
|
|
707
|
+
}
|
|
708
|
+
var COORD_COUNT2 = {
|
|
709
|
+
[PATH_M]: 2,
|
|
710
|
+
[PATH_L]: 2,
|
|
711
|
+
[PATH_C]: 6,
|
|
712
|
+
[PATH_Q]: 4,
|
|
713
|
+
[PATH_Z]: 0
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
// src/interactions/actions/resize/autoPoseDescriptor.ts
|
|
717
|
+
function isPathLike(p) {
|
|
718
|
+
return !!p && typeof p === "object" && "kind" in p && (p.kind === "polygon" || p.kind === "rect");
|
|
719
|
+
}
|
|
720
|
+
var AUTO_POSE_DESCRIPTOR = {
|
|
721
|
+
getBounds: (p) => isPathLike(p) ? pathPoseDescriptor.getBounds(p) : RECT_POSE_DESCRIPTOR.getBounds(p),
|
|
722
|
+
remapBounds: (p, src, dst) => isPathLike(p) ? pathPoseDescriptor.remapBounds(p, src, dst) : RECT_POSE_DESCRIPTOR.remapBounds(p, src, dst),
|
|
723
|
+
translate: (p, dx, dy) => isPathLike(p) ? pathPoseDescriptor.translate(p, dx, dy) : RECT_POSE_DESCRIPTOR.translate(p, dx, dy),
|
|
724
|
+
intersectsRect: (p, rect) => isPathLike(p) ? pathPoseDescriptor.intersectsRect(p, rect) : RECT_POSE_DESCRIPTOR.intersectsRect(p, rect),
|
|
725
|
+
getRotation: (p) => {
|
|
726
|
+
if (isPathLike(p)) return 0;
|
|
727
|
+
const r = p.rotation;
|
|
728
|
+
return typeof r === "number" ? r : 0;
|
|
729
|
+
},
|
|
730
|
+
// Path-shaped poses can't carry rotation (see `pathPoseDescriptor`).
|
|
731
|
+
// Everything else flows through `wrapWithPoseRotation`'s x/y/w/h gate at
|
|
732
|
+
// paint time, so report `true` and let the wrapper no-op for poses
|
|
733
|
+
// missing AABB fields.
|
|
734
|
+
supportsRotation: (p) => !isPathLike(p)
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
// src/interactions/gestures/shared/strategies/grid.ts
|
|
738
|
+
var RECT_ORIGIN_PROJECTION = {
|
|
739
|
+
getOrigin: (p) => ({ x: p.x, y: p.y }),
|
|
740
|
+
translate: (p, dx, dy) => ({ ...p, x: p.x + dx, y: p.y + dy })
|
|
741
|
+
};
|
|
742
|
+
var AUTO_ORIGIN_PROJECTION = {
|
|
743
|
+
getOrigin: (p) => {
|
|
744
|
+
if (isPathLike(p)) {
|
|
745
|
+
const b = boundsOfPath(p);
|
|
746
|
+
return { x: b.x, y: b.y };
|
|
747
|
+
}
|
|
748
|
+
const r = p;
|
|
749
|
+
return { x: r.x, y: r.y };
|
|
750
|
+
},
|
|
751
|
+
translate: (p, dx, dy) => {
|
|
752
|
+
if (isPathLike(p)) return translatePath(p, dx, dy);
|
|
753
|
+
const r = p;
|
|
754
|
+
return { ...r, x: r.x + dx, y: r.y + dy };
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
function gridSnapStrategy(spacing, arg) {
|
|
758
|
+
const isOpts = typeof arg === "object" && arg !== null && ("origin" in arg || "debug" in arg || "unitSystem" in arg) && !("base" in arg);
|
|
759
|
+
const optsArg = isOpts ? arg : null;
|
|
760
|
+
const unitSystem = optsArg ? optsArg.unitSystem : arg;
|
|
761
|
+
const proj = optsArg && optsArg.origin ? optsArg.origin : AUTO_ORIGIN_PROJECTION;
|
|
762
|
+
const debug = optsArg ? optsArg.debug : void 0;
|
|
763
|
+
const c = resolveUnit(spacing, unitSystem);
|
|
764
|
+
return {
|
|
765
|
+
snap(pose) {
|
|
766
|
+
const o = proj.getOrigin(pose);
|
|
767
|
+
const sx = Math.round(o.x / c) * c;
|
|
768
|
+
const sy = Math.round(o.y / c) * c;
|
|
769
|
+
debug?.recordSnapCandidate({ x: sx, y: sy }, true);
|
|
770
|
+
return proj.translate(pose, sx - o.x, sy - o.y);
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
function pointToGridCell(point, spacing, unitSystem, origin = { x: 0, y: 0 }) {
|
|
775
|
+
const s = resolveUnit(spacing, unitSystem);
|
|
776
|
+
return {
|
|
777
|
+
col: Math.floor((point.x - origin.x) / s),
|
|
778
|
+
row: Math.floor((point.y - origin.y) / s)
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// src/core/viewport/meanScale.ts
|
|
783
|
+
function meanScale(s) {
|
|
784
|
+
return Math.sqrt(s.x * s.y);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/interactions/gestures/shared/strategies/guides.ts
|
|
788
|
+
var DEFAULT_GUIDE_TOLERANCE_PX = 6;
|
|
789
|
+
function guideSnapStrategy(getGuides, options = {}) {
|
|
790
|
+
const tolerance = options.tolerance ?? DEFAULT_GUIDE_TOLERANCE_PX;
|
|
791
|
+
const getView = options.getView;
|
|
792
|
+
const proj = options.origin ?? RECT_ORIGIN_PROJECTION;
|
|
793
|
+
return {
|
|
794
|
+
snap(pose) {
|
|
795
|
+
const guides = getGuides();
|
|
796
|
+
if (guides.length === 0) return null;
|
|
797
|
+
const worldTolerance = getView ? tolerance / Math.max(1e-9, meanScale(getView().scale)) : tolerance;
|
|
798
|
+
const o = proj.getOrigin(pose);
|
|
799
|
+
let bestDx = 0;
|
|
800
|
+
let bestAbsDx = Infinity;
|
|
801
|
+
let bestDy = 0;
|
|
802
|
+
let bestAbsDy = Infinity;
|
|
803
|
+
for (const g of guides) {
|
|
804
|
+
if (g.axis === "x") {
|
|
805
|
+
const d = g.offset - o.x;
|
|
806
|
+
const ad = Math.abs(d);
|
|
807
|
+
if (ad <= worldTolerance && ad < bestAbsDx) {
|
|
808
|
+
bestAbsDx = ad;
|
|
809
|
+
bestDx = d;
|
|
810
|
+
}
|
|
811
|
+
} else {
|
|
812
|
+
const d = g.offset - o.y;
|
|
813
|
+
const ad = Math.abs(d);
|
|
814
|
+
if (ad <= worldTolerance && ad < bestAbsDy) {
|
|
815
|
+
bestAbsDy = ad;
|
|
816
|
+
bestDy = d;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (bestAbsDx === Infinity && bestAbsDy === Infinity) return null;
|
|
821
|
+
const dx = bestAbsDx === Infinity ? 0 : bestDx;
|
|
822
|
+
const dy = bestAbsDy === Infinity ? 0 : bestDy;
|
|
823
|
+
if (dx === 0 && dy === 0) return null;
|
|
824
|
+
return proj.translate(pose, dx, dy);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
export { AUTO_POSE_DESCRIPTOR, DEFAULT_FLATTEN_TOLERANCE, DEFAULT_GUIDE_TOLERANCE_PX, IMPERIAL_INCHES, METRIC_MM, PATH_C, PATH_CMD_LENGTHS, PATH_L, PATH_M, PATH_Q, PATH_Z, PIXELS, PathBuilder, RECT_ORIGIN_PROJECTION, RECT_POSE_DESCRIPTOR, ROTATED_POSE_DESCRIPTOR, boundsOfPath, ellipsePath, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, formatUnit, gridSnapStrategy, guideSnapStrategy, linePath, meanScale, pathPoseDescriptor, pointInPath, pointToGridCell, polygonFromPoints, rectPath, regularPolygonPath, resolveUnit, scalePathToBounds, starPath, strokeHitTest, transformPath, translatePath, translatePolygonInPlace };
|
|
830
|
+
//# sourceMappingURL=chunk-CQNKCG34.js.map
|
|
831
|
+
//# sourceMappingURL=chunk-CQNKCG34.js.map
|