@weasel-js/stroke 0.0.0-stage → 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/dist/index.js ADDED
@@ -0,0 +1,819 @@
1
+ import { getMarker } from './chunk-HZ37OTXN.js';
2
+ export { BUILTIN_MARKERS, getMarker, listMarkers, markerGeneration, markerRegistry, registerMarker } from './chunk-HZ37OTXN.js';
3
+ import { resolveScreenLength, alignedStrokeRect } from '@weasel-js/paint';
4
+ import { trimPolyline, extractPolylines } from '@weasel-js/geom/tessellate';
5
+ import { boundsOfPath } from '@weasel-js/geom';
6
+ import { trianglesToPath, pathIntersect, pathSubtract, pathUnion } from '@weasel-js/geom/booleans';
7
+
8
+ // src/tessellate/dash.ts
9
+ function splitForDash(pl, dash) {
10
+ const out = [];
11
+ const plA = pl.anchorA ?? new Uint32Array(pl.points.length / 2);
12
+ const plB = pl.anchorB ?? new Uint32Array(pl.points.length / 2);
13
+ const plT = pl.anchorT ?? new Float32Array(pl.points.length / 2);
14
+ let dashIdx = 0;
15
+ let dashRemaining = dash[0];
16
+ let onPhase = true;
17
+ let curPts = onPhase ? [pl.points[0], pl.points[1]] : null;
18
+ let curA = onPhase ? [plA[0]] : null;
19
+ let curB = onPhase ? [plB[0]] : null;
20
+ let curT = onPhase ? [plT[0]] : null;
21
+ const flushAndAdvance = () => {
22
+ if (curPts && curPts.length >= 4) {
23
+ out.push({
24
+ points: curPts,
25
+ closed: false,
26
+ anchorA: new Uint32Array(curA),
27
+ anchorB: new Uint32Array(curB),
28
+ anchorT: new Float32Array(curT)
29
+ });
30
+ }
31
+ curPts = null;
32
+ curA = null;
33
+ curB = null;
34
+ curT = null;
35
+ dashIdx = (dashIdx + 1) % dash.length;
36
+ dashRemaining = dash[dashIdx];
37
+ onPhase = !onPhase;
38
+ };
39
+ let prevX = pl.points[0], prevY = pl.points[1];
40
+ const ptCount = pl.points.length / 2;
41
+ const segCount = pl.closed ? ptCount : ptCount - 1;
42
+ for (let i = 0; i < segCount; i++) {
43
+ const nextIdx = (i + 1) % ptCount;
44
+ const cx = pl.points[nextIdx * 2], cy = pl.points[nextIdx * 2 + 1];
45
+ const startA = plA[i], startB = plB[i], startT = plT[i];
46
+ const endA = plA[nextIdx], endB = plB[nextIdx], endT = plT[nextIdx];
47
+ const segFullDx = cx - prevX, segFullDy = cy - prevY;
48
+ const segFullLen = Math.hypot(segFullDx, segFullDy);
49
+ let segDx = segFullDx, segDy = segFullDy;
50
+ let segLen = segFullLen;
51
+ let traveled = 0;
52
+ while (segLen > 1e-9) {
53
+ if (segLen <= dashRemaining) {
54
+ if (onPhase && curPts) {
55
+ curPts.push(cx, cy);
56
+ curA.push(endA);
57
+ curB.push(endB);
58
+ curT.push(endT);
59
+ }
60
+ dashRemaining -= segLen;
61
+ prevX = cx;
62
+ prevY = cy;
63
+ traveled = segFullLen;
64
+ segLen = 0;
65
+ if (dashRemaining <= 1e-9) {
66
+ flushAndAdvance();
67
+ if (onPhase) {
68
+ curPts = [prevX, prevY];
69
+ curA = [endA];
70
+ curB = [endB];
71
+ curT = [endT];
72
+ }
73
+ }
74
+ } else {
75
+ const tConsume = dashRemaining / segLen;
76
+ const ix = prevX + segDx * tConsume;
77
+ const iy = prevY + segDy * tConsume;
78
+ traveled += dashRemaining;
79
+ const frac = traveled / segFullLen;
80
+ let mA, mB, mT;
81
+ if (startA === endA && startB === endB) {
82
+ mA = startA;
83
+ mB = startB;
84
+ mT = startT + (endT - startT) * frac;
85
+ } else {
86
+ if (frac < 0.5) {
87
+ mA = startA;
88
+ mB = startB;
89
+ mT = startT;
90
+ } else {
91
+ mA = endA;
92
+ mB = endB;
93
+ mT = endT;
94
+ }
95
+ }
96
+ if (onPhase && curPts) {
97
+ curPts.push(ix, iy);
98
+ curA.push(mA);
99
+ curB.push(mB);
100
+ curT.push(mT);
101
+ }
102
+ prevX = ix;
103
+ prevY = iy;
104
+ segDx = cx - prevX;
105
+ segDy = cy - prevY;
106
+ segLen = Math.hypot(segDx, segDy);
107
+ flushAndAdvance();
108
+ if (onPhase) {
109
+ curPts = [prevX, prevY];
110
+ curA = [mA];
111
+ curB = [mB];
112
+ curT = [mT];
113
+ }
114
+ }
115
+ }
116
+ }
117
+ if (curPts && curPts.length >= 4) {
118
+ const tail = {
119
+ points: curPts,
120
+ closed: false,
121
+ anchorA: new Uint32Array(curA),
122
+ anchorB: new Uint32Array(curB),
123
+ anchorT: new Float32Array(curT)
124
+ };
125
+ const tx = curPts[curPts.length - 2], ty = curPts[curPts.length - 1];
126
+ const meets = (x, y) => Math.hypot(tx - x, ty - y) <= 1e-9;
127
+ if (pl.closed && out.length > 0 && meets(out[0].points[0], out[0].points[1])) {
128
+ out[0] = joinAtSeam(tail, out[0]);
129
+ } else if (pl.closed && out.length === 0 && meets(curPts[0], curPts[1])) {
130
+ out.push({
131
+ points: curPts.slice(0, -2),
132
+ closed: true,
133
+ anchorA: tail.anchorA.slice(0, -1),
134
+ anchorB: tail.anchorB.slice(0, -1),
135
+ anchorT: tail.anchorT.slice(0, -1)
136
+ });
137
+ } else {
138
+ out.push(tail);
139
+ }
140
+ }
141
+ return out;
142
+ }
143
+ function joinAtSeam(tail, head) {
144
+ return {
145
+ points: [...tail.points, ...head.points.slice(2)],
146
+ closed: false,
147
+ anchorA: Uint32Array.from([...tail.anchorA, ...head.anchorA.subarray(1)]),
148
+ anchorB: Uint32Array.from([...tail.anchorB, ...head.anchorB.subarray(1)]),
149
+ anchorT: Float32Array.from([...tail.anchorT, ...head.anchorT.subarray(1)])
150
+ };
151
+ }
152
+
153
+ // src/tessellate/vertexWidths.ts
154
+ function populatePolylineWidths(pl, vertexWidths, fallbackWidth, scale) {
155
+ const ptCount = pl.points.length / 2;
156
+ if (ptCount === 0) return;
157
+ const aA = pl.anchorA ?? new Uint32Array(ptCount);
158
+ const aB = pl.anchorB ?? new Uint32Array(ptCount);
159
+ const aT = pl.anchorT ?? new Float32Array(ptCount);
160
+ const widths = new Float32Array(ptCount);
161
+ let k = 1;
162
+ const read = (i) => {
163
+ const v = vertexWidths[i];
164
+ return typeof v === "number" && isFinite(v) && v > 0 ? v * k : fallbackWidth;
165
+ };
166
+ for (let i = 0; i < ptCount; i++) {
167
+ if (scale) k = scaleAt(pl, i, scale);
168
+ const a = aA[i], b = aB[i], t = aT[i];
169
+ if (a === b) {
170
+ widths[i] = read(a);
171
+ } else {
172
+ const wa = read(a);
173
+ const wb = read(b);
174
+ widths[i] = wa + (wb - wa) * t;
175
+ }
176
+ }
177
+ pl.widths = widths;
178
+ }
179
+ function scaleAt(pl, i, scale) {
180
+ const pts = pl.points;
181
+ const n = pts.length / 2;
182
+ let tx = 0, ty = 0;
183
+ const add = (from, to) => {
184
+ const dx = pts[to * 2] - pts[from * 2], dy = pts[to * 2 + 1] - pts[from * 2 + 1];
185
+ const len2 = Math.hypot(dx, dy);
186
+ if (len2 > 0) {
187
+ tx += dx / len2;
188
+ ty += dy / len2;
189
+ }
190
+ };
191
+ if (i > 0) add(i - 1, i);
192
+ else if (pl.closed && n > 1) add(n - 1, 0);
193
+ if (i < n - 1) add(i, i + 1);
194
+ else if (pl.closed && n > 1) add(n - 1, 0);
195
+ const len = Math.hypot(tx, ty);
196
+ return len > 0 ? scale(tx / len, ty / len) : 1;
197
+ }
198
+
199
+ // src/tessellate/metric.ts
200
+ function screenSpaceKey(space) {
201
+ return space === void 0 ? "" : `${metricKey(space.metric)}@${space.pxPerUnit}`;
202
+ }
203
+ var CONFORMAL_EPSILON = 1e-9;
204
+ function strokeSpaceOf(a, b, c, d) {
205
+ const det2 = Math.abs(a * d - b * c);
206
+ const scale = Math.sqrt(det2);
207
+ if (!(det2 > 0) || !Number.isFinite(det2)) return { scale: 0, metric: null };
208
+ const g11 = (a * a + b * b) / det2;
209
+ const g12 = (a * c + b * d) / det2;
210
+ const g22 = (c * c + d * d) / det2;
211
+ const s = Math.sqrt(g11 + g22 + 2);
212
+ const p = (g11 + 1) / s;
213
+ const q = g12 / s;
214
+ const r = (g22 + 1) / s;
215
+ if (Math.abs(q) < CONFORMAL_EPSILON && Math.abs(p - r) < CONFORMAL_EPSILON) {
216
+ return { scale, metric: null };
217
+ }
218
+ return { scale, metric: { p, q, r } };
219
+ }
220
+ function metricKey(metric) {
221
+ return metric === void 0 ? "" : `${metric.p},${metric.q},${metric.r}`;
222
+ }
223
+ function polylineIntoMetric(pl, m) {
224
+ const pts = pl.points;
225
+ for (let i = 0; i < pts.length; i += 2) {
226
+ const x = pts[i], y = pts[i + 1];
227
+ pts[i] = m.p * x + m.q * y;
228
+ pts[i + 1] = m.q * x + m.r * y;
229
+ }
230
+ }
231
+ function polylineOutOfMetric(pl, m) {
232
+ const pts = pl.points;
233
+ for (let i = 0; i < pts.length; i += 2) {
234
+ const x = pts[i], y = pts[i + 1];
235
+ pts[i] = m.r * x - m.q * y;
236
+ pts[i + 1] = m.p * y - m.q * x;
237
+ }
238
+ }
239
+ function sameMetric(a, b) {
240
+ if (a === b) return true;
241
+ return a !== null && b !== null && a.p === b.p && a.q === b.q && a.r === b.r;
242
+ }
243
+ function worldWidthInMetric(m, tx, ty) {
244
+ return Math.hypot(m.r * tx - m.q * ty, m.p * ty - m.q * tx);
245
+ }
246
+ function worldWidthInMetricAlongWorld(m, ux, uy) {
247
+ return 1 / Math.hypot(m.p * ux + m.q * uy, m.q * ux + m.r * uy);
248
+ }
249
+ function coordsOutOfMetric(coords, m) {
250
+ for (let i = 0; i < coords.length; i += 2) {
251
+ const x = coords[i], y = coords[i + 1];
252
+ coords[i] = m.r * x - m.q * y;
253
+ coords[i + 1] = m.p * y - m.q * x;
254
+ }
255
+ }
256
+ function metricKeepsRects(m) {
257
+ return m.q === 0;
258
+ }
259
+ function rectIntoMetric(rect, m) {
260
+ if (!metricKeepsRects(m)) return null;
261
+ return { kind: "rect", x: m.p * rect.x, y: m.r * rect.y, width: m.p * rect.width, height: m.r * rect.height };
262
+ }
263
+
264
+ // src/tessellate/insets.ts
265
+ var trimOf = (inset, ribbon) => inset === void 0 ? { length: 0, metric: ribbon } : typeof inset === "number" ? { length: inset, metric: ribbon } : inset;
266
+ function trimInSpace(pl, from, ribbon, start, end) {
267
+ const s = trimOf(start, ribbon);
268
+ const e = trimOf(end, ribbon);
269
+ let cur = pl;
270
+ let space = from;
271
+ const moveTo = (to) => {
272
+ if (cur === null || sameMetric(space, to)) return;
273
+ if (space !== null) polylineOutOfMetric(cur, space);
274
+ if (to !== null) polylineIntoMetric(cur, to);
275
+ space = to;
276
+ };
277
+ if (s.length > 0 || e.length > 0) {
278
+ if (sameMetric(s.metric, e.metric)) {
279
+ moveTo(s.metric);
280
+ cur = trimPolyline(cur, s.length, e.length);
281
+ } else {
282
+ for (const [len, metric, atStart] of [[s.length, s.metric, true], [e.length, e.metric, false]]) {
283
+ if (!(len > 0) || cur === null) continue;
284
+ moveTo(metric);
285
+ cur = trimPolyline(cur, atStart ? len : 0, atStart ? 0 : len);
286
+ }
287
+ }
288
+ }
289
+ moveTo(ribbon);
290
+ return cur;
291
+ }
292
+
293
+ // src/tessellate/stroke.ts
294
+ var EMPTY_MESH = {
295
+ vertices: new Float32Array(0),
296
+ indices: new Uint32Array(0),
297
+ anchorA: new Uint32Array(0),
298
+ anchorB: new Uint32Array(0),
299
+ anchorT: new Float32Array(0)
300
+ };
301
+ function resolveStrokeWidth(width, scale) {
302
+ return resolveScreenLength(width, scale);
303
+ }
304
+ function tessellateStroke(path, stroke, opts = {}) {
305
+ const width = resolveStrokeWidth(stroke.width ?? 1, 1);
306
+ if (width <= 0) return EMPTY_MESH;
307
+ const join = stroke.join ?? "miter";
308
+ const cap = stroke.cap ?? "butt";
309
+ const align = stroke.align ?? "center";
310
+ const miterLimit = stroke.miterLimit ?? DEFAULT_MITER_LIMIT;
311
+ const varyingThreshold = stroke.varyingWidthJoinThreshold ?? DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD;
312
+ const metric = opts.metric;
313
+ const rectInMetric = metric && path.kind === "rect" ? rectIntoMetric(path, metric) : null;
314
+ let workingPath = rectInMetric ?? path;
315
+ if (workingPath.kind === "rect" && align !== "center") {
316
+ const aligned = alignedStrokeRect(workingPath, align, width);
317
+ workingPath = {
318
+ kind: "rect",
319
+ x: aligned.x,
320
+ y: aligned.y,
321
+ width: aligned.width,
322
+ height: aligned.height
323
+ };
324
+ }
325
+ const polylines = extractPolylines(workingPath, opts);
326
+ const vertexWidths = stroke.vertexWidths && stroke.vertexWidths.length > 0 ? stroke.vertexWidths : null;
327
+ const scale = !metric ? void 0 : rectInMetric ? (tx, ty) => worldWidthInMetric(metric, tx, ty) : (ux, uy) => worldWidthInMetricAlongWorld(metric, ux, uy);
328
+ const dash = stroke.dash ?? [];
329
+ const verts = [];
330
+ const idx = [];
331
+ const anchorA = [];
332
+ const anchorB = [];
333
+ const anchorT = [];
334
+ const ribbonSpace = metric ?? null;
335
+ for (const pl of polylines) {
336
+ if (vertexWidths) populatePolylineWidths(pl, vertexWidths, width, scale);
337
+ const trimmed = trimInSpace(pl, rectInMetric ? ribbonSpace : null, ribbonSpace, opts.startInset, opts.endInset);
338
+ if (trimmed === null) continue;
339
+ const subs = dash.length > 0 ? splitForDash(trimmed, dash) : [trimmed];
340
+ for (const sub of subs) {
341
+ expandPolyline(sub, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT);
342
+ }
343
+ }
344
+ const vertices = new Float32Array(verts);
345
+ if (metric) coordsOutOfMetric(vertices, metric);
346
+ return {
347
+ vertices,
348
+ indices: new Uint32Array(idx),
349
+ anchorA: new Uint32Array(anchorA),
350
+ anchorB: new Uint32Array(anchorB),
351
+ anchorT: new Float32Array(anchorT)
352
+ };
353
+ }
354
+ function expandPolyline(pl, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT) {
355
+ const half = width / 2;
356
+ const pts = pl.points;
357
+ const ptCount = pts.length / 2;
358
+ const segCount = ptCount - 1;
359
+ if (segCount < 1) return;
360
+ const plA = pl.anchorA ?? new Uint32Array(ptCount);
361
+ const plB = pl.anchorB ?? new Uint32Array(ptCount);
362
+ const plT = pl.anchorT ?? new Float32Array(ptCount);
363
+ const plHalf = pl.widths ? pl.widths.map((w) => w / 2) : null;
364
+ const segs = [];
365
+ const segSrcIdx = [];
366
+ for (let s = 0; s < segCount; s++) {
367
+ const ha = plHalf ? plHalf[s] : half;
368
+ const hb = plHalf ? plHalf[s + 1] : half;
369
+ const seg = makeSeg(pts[s * 2], pts[s * 2 + 1], pts[(s + 1) * 2], pts[(s + 1) * 2 + 1], ha, hb);
370
+ if (seg) {
371
+ segs.push(seg);
372
+ segSrcIdx.push(s);
373
+ }
374
+ }
375
+ let closerSrcIdx = -1;
376
+ if (pl.closed && segs.length >= 1) {
377
+ const last = segs[segs.length - 1];
378
+ const first = segs[0];
379
+ const haCloser = plHalf ? plHalf[ptCount - 1] : half;
380
+ const hbCloser = plHalf ? plHalf[0] : half;
381
+ const closer = makeSeg(last.bx, last.by, first.ax, first.ay, haCloser, hbCloser);
382
+ if (closer) {
383
+ segs.push(closer);
384
+ closerSrcIdx = ptCount - 1;
385
+ segSrcIdx.push(closerSrcIdx);
386
+ }
387
+ }
388
+ if (segs.length === 0) return;
389
+ const segBaseIdx = [];
390
+ for (let s = 0; s < segs.length; s++) {
391
+ const seg = segs[s];
392
+ const startSrc = segSrcIdx[s];
393
+ const endSrc = s === segs.length - 1 && pl.closed && closerSrcIdx >= 0 ? 0 : startSrc + 1;
394
+ const base = verts.length / 2;
395
+ segBaseIdx.push(base);
396
+ const nxA = seg.nxUnit * seg.halfA, nyA = seg.nyUnit * seg.halfA;
397
+ const nxB = seg.nxUnit * seg.halfB, nyB = seg.nyUnit * seg.halfB;
398
+ verts.push(seg.ax + nxA, seg.ay + nyA);
399
+ anchorA.push(plA[startSrc]);
400
+ anchorB.push(plB[startSrc]);
401
+ anchorT.push(plT[startSrc]);
402
+ verts.push(seg.ax - nxA, seg.ay - nyA);
403
+ anchorA.push(plA[startSrc]);
404
+ anchorB.push(plB[startSrc]);
405
+ anchorT.push(plT[startSrc]);
406
+ verts.push(seg.bx + nxB, seg.by + nyB);
407
+ anchorA.push(plA[endSrc]);
408
+ anchorB.push(plB[endSrc]);
409
+ anchorT.push(plT[endSrc]);
410
+ verts.push(seg.bx - nxB, seg.by - nyB);
411
+ anchorA.push(plA[endSrc]);
412
+ anchorB.push(plB[endSrc]);
413
+ anchorT.push(plT[endSrc]);
414
+ idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
415
+ }
416
+ const joinCount = pl.closed ? segs.length : segs.length - 1;
417
+ for (let j = 0; j < joinCount; j++) {
418
+ emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT);
419
+ }
420
+ if (!pl.closed && cap !== "butt") {
421
+ const first = segs[0];
422
+ const firstBase = segBaseIdx[0];
423
+ emitCap(first, firstBase + 0, firstBase + 1, false, first.halfA, cap, verts, idx, anchorA, anchorB, anchorT, plA[0], plB[0], plT[0]);
424
+ const last = segs[segs.length - 1];
425
+ const lastBase = segBaseIdx[segs.length - 1];
426
+ const lastSrc = segSrcIdx[segs.length - 1] + 1;
427
+ emitCap(last, lastBase + 2, lastBase + 3, true, last.halfB, cap, verts, idx, anchorA, anchorB, anchorT, plA[lastSrc], plB[lastSrc], plT[lastSrc]);
428
+ }
429
+ }
430
+ var ROUND_CAP_STEP_RAD = 10 * Math.PI / 180;
431
+ function emitCap(seg, leftIdx, rightIdx, atEnd, half, cap, verts, idx, anchorA, anchorB, anchorT, endA, endB, endT) {
432
+ const sign = atEnd ? 1 : -1;
433
+ const dx = (seg.bx - seg.ax) / seg.len * sign;
434
+ const dy = (seg.by - seg.ay) / seg.len * sign;
435
+ const cx = atEnd ? seg.bx : seg.ax;
436
+ const cy = atEnd ? seg.by : seg.ay;
437
+ const nx = seg.nxUnit * half;
438
+ const ny = seg.nyUnit * half;
439
+ if (cap === "square") {
440
+ const ox = cx + dx * half;
441
+ const oy = cy + dy * half;
442
+ const lOut = verts.length / 2;
443
+ verts.push(ox + nx, oy + ny);
444
+ anchorA.push(endA);
445
+ anchorB.push(endB);
446
+ anchorT.push(endT);
447
+ const rOut = verts.length / 2;
448
+ verts.push(ox - nx, oy - ny);
449
+ anchorA.push(endA);
450
+ anchorB.push(endB);
451
+ anchorT.push(endT);
452
+ if (atEnd) {
453
+ idx.push(leftIdx, lOut, rOut, leftIdx, rOut, rightIdx);
454
+ } else {
455
+ idx.push(leftIdx, rOut, lOut, leftIdx, rightIdx, rOut);
456
+ }
457
+ return;
458
+ }
459
+ if (cap === "round") {
460
+ const pivotIdx = verts.length / 2;
461
+ verts.push(cx, cy);
462
+ anchorA.push(endA);
463
+ anchorB.push(endB);
464
+ anchorT.push(endT);
465
+ const startAngle = Math.atan2(ny, nx);
466
+ const sweep = atEnd ? -Math.PI : Math.PI;
467
+ const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_CAP_STEP_RAD));
468
+ const stepAngle = sweep / steps;
469
+ let prevIdx = leftIdx;
470
+ for (let i = 1; i < steps; i++) {
471
+ const ang = startAngle + i * stepAngle;
472
+ const fx = cx + Math.cos(ang) * half;
473
+ const fy = cy + Math.sin(ang) * half;
474
+ const newIdx = verts.length / 2;
475
+ verts.push(fx, fy);
476
+ anchorA.push(endA);
477
+ anchorB.push(endB);
478
+ anchorT.push(endT);
479
+ if (atEnd) idx.push(prevIdx, newIdx, pivotIdx);
480
+ else idx.push(prevIdx, pivotIdx, newIdx);
481
+ prevIdx = newIdx;
482
+ }
483
+ if (atEnd) idx.push(prevIdx, rightIdx, pivotIdx);
484
+ else idx.push(prevIdx, pivotIdx, rightIdx);
485
+ }
486
+ }
487
+ function makeSeg(ax, ay, bx, by, halfA, halfB) {
488
+ const dx = bx - ax, dy = by - ay;
489
+ const len = Math.hypot(dx, dy);
490
+ if (len === 0) return null;
491
+ return {
492
+ ax,
493
+ ay,
494
+ bx,
495
+ by,
496
+ nxUnit: -dy / len,
497
+ nyUnit: dx / len,
498
+ halfA,
499
+ halfB,
500
+ len
501
+ };
502
+ }
503
+ var DEFAULT_MITER_LIMIT = 4;
504
+ var DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD = 1.5;
505
+ function emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT) {
506
+ const a = segs[j];
507
+ const b = segs[(j + 1) % segs.length];
508
+ const aBase = segBaseIdx[j];
509
+ const bBase = segBaseIdx[(j + 1) % segs.length];
510
+ const adx = a.bx - a.ax, ady = a.by - a.ay;
511
+ const bdx = b.bx - b.ax, bdy = b.by - b.ay;
512
+ const cross = adx * bdy - ady * bdx;
513
+ if (cross === 0) return;
514
+ const onPositive = cross > 0;
515
+ const aOuterEnd = onPositive ? aBase + 3 : aBase + 2;
516
+ const bOuterStart = onPositive ? bBase + 1 : bBase + 0;
517
+ const nextSeg = (j + 1) % segs.length;
518
+ const cornerSrc = segSrcIdx[nextSeg];
519
+ const cornerA = plA[cornerSrc];
520
+ const cornerB = plB[cornerSrc];
521
+ const cornerT = plT[cornerSrc];
522
+ const halfAtCorner = a.halfB;
523
+ const taperA = Math.max(a.halfA, a.halfB) / Math.max(1e-9, Math.min(a.halfA, a.halfB));
524
+ const taperB = Math.max(b.halfA, b.halfB) / Math.max(1e-9, Math.min(b.halfA, b.halfB));
525
+ const forceBevelForVarying = taperA > varyingThreshold || taperB > varyingThreshold;
526
+ if (join === "bevel" || forceBevelForVarying) {
527
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
528
+ return;
529
+ }
530
+ if (join === "miter") {
531
+ const aOX = verts[aOuterEnd * 2], aOY = verts[aOuterEnd * 2 + 1];
532
+ const bOX = verts[bOuterStart * 2], bOY = verts[bOuterStart * 2 + 1];
533
+ const apex = lineLineIntersect(aOX, aOY, adx, ady, bOX, bOY, -bdx, -bdy);
534
+ if (!apex) {
535
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
536
+ return;
537
+ }
538
+ const miterLen = Math.hypot(apex[0] - a.bx, apex[1] - a.by);
539
+ if (miterLen > miterLimit * halfAtCorner) {
540
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
541
+ return;
542
+ }
543
+ const apexIdx = verts.length / 2;
544
+ verts.push(apex[0], apex[1]);
545
+ anchorA.push(cornerA);
546
+ anchorB.push(cornerB);
547
+ anchorT.push(cornerT);
548
+ const jIdx = verts.length / 2;
549
+ verts.push(a.bx, a.by);
550
+ anchorA.push(cornerA);
551
+ anchorB.push(cornerB);
552
+ anchorT.push(cornerT);
553
+ if (onPositive) {
554
+ idx.push(aOuterEnd, apexIdx, bOuterStart);
555
+ idx.push(aOuterEnd, bOuterStart, jIdx);
556
+ } else {
557
+ idx.push(aOuterEnd, bOuterStart, apexIdx);
558
+ idx.push(aOuterEnd, jIdx, bOuterStart);
559
+ }
560
+ return;
561
+ }
562
+ if (join === "round") {
563
+ emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
564
+ return;
565
+ }
566
+ }
567
+ var ROUND_STEP_RAD = 10 * Math.PI / 180;
568
+ function emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
569
+ const cx = a.bx, cy = a.by;
570
+ const pivotIdx = verts.length / 2;
571
+ verts.push(cx, cy);
572
+ anchorA.push(cornerA);
573
+ anchorB.push(cornerB);
574
+ anchorT.push(cornerT);
575
+ const startX = verts[aOuterEnd * 2] - cx;
576
+ const startY = verts[aOuterEnd * 2 + 1] - cy;
577
+ const endX = verts[bOuterStart * 2] - cx;
578
+ const endY = verts[bOuterStart * 2 + 1] - cy;
579
+ const r = Math.hypot(startX, startY);
580
+ const startAngle = Math.atan2(startY, startX);
581
+ const endAngle = Math.atan2(endY, endX);
582
+ let sweep = endAngle - startAngle;
583
+ if (onPositive && sweep < 0) sweep += 2 * Math.PI;
584
+ else if (!onPositive && sweep > 0) sweep -= 2 * Math.PI;
585
+ const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_STEP_RAD));
586
+ const stepAngle = sweep / steps;
587
+ let prevIdx = aOuterEnd;
588
+ for (let i = 1; i < steps; i++) {
589
+ const ang = startAngle + i * stepAngle;
590
+ const fx = cx + Math.cos(ang) * r;
591
+ const fy = cy + Math.sin(ang) * r;
592
+ const newIdx = verts.length / 2;
593
+ verts.push(fx, fy);
594
+ anchorA.push(cornerA);
595
+ anchorB.push(cornerB);
596
+ anchorT.push(cornerT);
597
+ if (onPositive) idx.push(prevIdx, pivotIdx, newIdx);
598
+ else idx.push(prevIdx, newIdx, pivotIdx);
599
+ prevIdx = newIdx;
600
+ }
601
+ if (onPositive) idx.push(prevIdx, pivotIdx, bOuterStart);
602
+ else idx.push(prevIdx, bOuterStart, pivotIdx);
603
+ }
604
+ function emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
605
+ const jIdx = verts.length / 2;
606
+ verts.push(a.bx, a.by);
607
+ anchorA.push(cornerA);
608
+ anchorB.push(cornerB);
609
+ anchorT.push(cornerT);
610
+ if (onPositive) idx.push(aOuterEnd, jIdx, bOuterStart);
611
+ else idx.push(aOuterEnd, bOuterStart, jIdx);
612
+ }
613
+ function lineLineIntersect(apx, apy, adx, ady, bpx, bpy, bdx, bdy) {
614
+ const denom = adx * bdy - ady * bdx;
615
+ if (Math.abs(denom) < 1e-9) return null;
616
+ const t = ((bpx - apx) * bdy - (bpy - apy) * bdx) / denom;
617
+ return [apx + t * adx, apy + t * ady];
618
+ }
619
+ function resolveMarkerSize(ref, strokeWidth, scale = 1) {
620
+ if (typeof ref === "string" || ref.size === void 0) return strokeWidth;
621
+ return resolveScreenLength(ref.size, scale);
622
+ }
623
+ function markerKeyOf(ref) {
624
+ return typeof ref === "string" ? ref : ref.key;
625
+ }
626
+ function markerInset(ref, strokeWidth, scale = 1) {
627
+ if (ref === void 0) return 0;
628
+ const entry = getMarker(markerKeyOf(ref));
629
+ if (entry === void 0) return 0;
630
+ return (entry.inset ?? 0) * resolveMarkerSize(ref, strokeWidth, scale);
631
+ }
632
+ function strokeInsets(stroke, strokeWidth) {
633
+ return {
634
+ start: markerInset(stroke.markerStart, strokeWidth),
635
+ end: markerInset(stroke.markerEnd, strokeWidth)
636
+ };
637
+ }
638
+
639
+ // src/markerSites.ts
640
+ var EPS = 1e-9;
641
+ function dir(pl, i, j) {
642
+ const dx = pl.points[j * 2] - pl.points[i * 2];
643
+ const dy = pl.points[j * 2 + 1] - pl.points[i * 2 + 1];
644
+ const len = Math.hypot(dx, dy);
645
+ if (len < EPS) return null;
646
+ return { x: dx / len, y: dy / len };
647
+ }
648
+ function nextDistinct(pl, i, n) {
649
+ for (let k = i + 1; k < n; k++) if (dir(pl, i, k)) return k;
650
+ return -1;
651
+ }
652
+ function prevDistinct(pl, i) {
653
+ for (let k = i - 1; k >= 0; k--) if (dir(pl, k, i)) return k;
654
+ return -1;
655
+ }
656
+ function markerSites(pl, want) {
657
+ const out = [];
658
+ const n = pl.points.length / 2;
659
+ if (n < 2) return out;
660
+ if (want.start && !pl.closed) {
661
+ const j = nextDistinct(pl, 0, n);
662
+ const d = j >= 0 ? dir(pl, 0, j) : null;
663
+ if (d) {
664
+ out.push({ x: pl.points[0], y: pl.points[1], angle: Math.atan2(-d.y, -d.x), role: "start" });
665
+ }
666
+ }
667
+ if (want.mid) {
668
+ const A = pl.anchorA, B = pl.anchorB, T = pl.anchorT;
669
+ if (A && B && T) {
670
+ for (let i = 1; i < n - 1; i++) {
671
+ if (A[i] !== B[i] || T[i] !== 0) continue;
672
+ const p = prevDistinct(pl, i);
673
+ const q = nextDistinct(pl, i, n);
674
+ if (p < 0 || q < 0) continue;
675
+ const inDir = dir(pl, p, i);
676
+ const outDir = dir(pl, i, q);
677
+ let bx = inDir.x + outDir.x;
678
+ let by = inDir.y + outDir.y;
679
+ if (Math.hypot(bx, by) < EPS) {
680
+ bx = inDir.x;
681
+ by = inDir.y;
682
+ }
683
+ out.push({ x: pl.points[i * 2], y: pl.points[i * 2 + 1], angle: Math.atan2(by, bx), role: "mid" });
684
+ }
685
+ }
686
+ }
687
+ if (want.end && !pl.closed) {
688
+ const last = n - 1;
689
+ const p = prevDistinct(pl, last);
690
+ const d = p >= 0 ? dir(pl, p, last) : null;
691
+ if (d) {
692
+ out.push({ x: pl.points[last * 2], y: pl.points[last * 2 + 1], angle: Math.atan2(d.y, d.x), role: "end" });
693
+ }
694
+ }
695
+ return out;
696
+ }
697
+ function placed(path, site) {
698
+ const src = path;
699
+ const cos = Math.cos(site.angle);
700
+ const sin = Math.sin(site.angle);
701
+ const coords = new Float32Array(src.coords.length);
702
+ for (let i = 0; i < src.coords.length; i += 2) {
703
+ const x = src.coords[i], y = src.coords[i + 1];
704
+ coords[i] = site.x + x * cos - y * sin;
705
+ coords[i + 1] = site.y + x * sin + y * cos;
706
+ }
707
+ return { kind: "polygon", commands: src.commands, coords, fillRule: src.fillRule };
708
+ }
709
+ function markerHeads(path, stroke, strokeWidth, flattenTolerance, spaces = {}) {
710
+ const refs = { start: stroke.markerStart, mid: stroke.markerMid, end: stroke.markerEnd };
711
+ if (refs.start === void 0 && refs.mid === void 0 && refs.end === void 0) return [];
712
+ const groups = /* @__PURE__ */ new Map();
713
+ for (const role of ["start", "mid", "end"]) {
714
+ if (refs[role] === void 0) continue;
715
+ const space = spaces[role];
716
+ let want = groups.get(space);
717
+ if (want === void 0) groups.set(space, want = { start: false, mid: false, end: false });
718
+ want[role] = true;
719
+ }
720
+ const out = [];
721
+ for (const pl of extractPolylines(path, { flattenTolerance })) {
722
+ for (const [space, want] of groups) {
723
+ const inSpace = space ? { ...pl, points: pl.points.slice() } : pl;
724
+ if (space) polylineIntoMetric(inSpace, space.metric);
725
+ for (const site of markerSites(inSpace, want)) {
726
+ const ref = refs[site.role];
727
+ if (ref === void 0) continue;
728
+ const entry = getMarker(markerKeyOf(ref));
729
+ if (entry === void 0) continue;
730
+ const size = resolveMarkerSize(ref, strokeWidth);
731
+ const angle = entry.orient === void 0 || entry.orient === "auto" ? site.angle : entry.orient;
732
+ const head = placed(entry.path({ size, stroke }), { ...site, angle });
733
+ if (space) coordsOutOfMetric(head.coords, space.metric);
734
+ out.push(space ? { entry, size, path: head, space } : { entry, size, path: head });
735
+ }
736
+ }
737
+ }
738
+ return out;
739
+ }
740
+ function markerReadsKey(entry, stroke) {
741
+ if (entry.reads === void 0) return null;
742
+ let key = "";
743
+ for (const field of entry.reads) key += `${JSON.stringify(stroke[field]) ?? ""}|`;
744
+ return key;
745
+ }
746
+ var READINGS_PER_ENTRY = 64;
747
+ var REACH_BY_READING = /* @__PURE__ */ new WeakMap();
748
+ var REACH_BY_STROKE = /* @__PURE__ */ new WeakMap();
749
+ function measureUnitReach(entry, stroke) {
750
+ const b = boundsOfPath(entry.path({ size: 1, stroke }));
751
+ return Math.max(Math.abs(b.x), Math.abs(b.x + b.width), Math.abs(b.y), Math.abs(b.y + b.height)) + (entry.outline ? entry.outline.width / 2 : 0);
752
+ }
753
+ function unitReach(entry, stroke) {
754
+ const key = markerReadsKey(entry, stroke);
755
+ if (key === null) {
756
+ let byStroke = REACH_BY_STROKE.get(entry);
757
+ if (byStroke === void 0) REACH_BY_STROKE.set(entry, byStroke = /* @__PURE__ */ new WeakMap());
758
+ let reach2 = byStroke.get(stroke);
759
+ if (reach2 === void 0) byStroke.set(stroke, reach2 = measureUnitReach(entry, stroke));
760
+ return reach2;
761
+ }
762
+ let byReading = REACH_BY_READING.get(entry);
763
+ if (byReading === void 0) REACH_BY_READING.set(entry, byReading = /* @__PURE__ */ new Map());
764
+ let reach = byReading.get(key);
765
+ if (reach === void 0) {
766
+ if (byReading.size >= READINGS_PER_ENTRY) byReading.clear();
767
+ byReading.set(key, reach = measureUnitReach(entry, stroke));
768
+ }
769
+ return reach;
770
+ }
771
+ function markerReach(stroke) {
772
+ const reach = { world: 0, px: 0 };
773
+ const width = stroke.width ?? 1;
774
+ for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {
775
+ if (ref === void 0) continue;
776
+ const entry = getMarker(markerKeyOf(ref));
777
+ if (entry === void 0) continue;
778
+ const size = typeof ref === "string" || ref.size === void 0 ? width : ref.size;
779
+ const unit = unitReach(entry, stroke);
780
+ if (typeof size === "number") reach.world = Math.max(reach.world, unit * size);
781
+ else reach.px = Math.max(reach.px, unit * size.px);
782
+ }
783
+ return reach;
784
+ }
785
+ var EMPTY = {
786
+ kind: "polygon",
787
+ commands: new Uint8Array(0),
788
+ coords: new Float32Array(0),
789
+ fillRule: "nonzero"
790
+ };
791
+ function strokeToPath(path, stroke, opts = {}) {
792
+ const width = resolveStrokeWidth(stroke.width ?? 1, opts.scale ?? 1);
793
+ if (!(width > 0)) return EMPTY;
794
+ const tolerance = opts.flattenTolerance;
795
+ const sized = { ...stroke, width };
796
+ const insets = strokeInsets(sized, width);
797
+ const align = stroke.align ?? "center";
798
+ const cut = align !== "center" && path.kind !== "rect";
799
+ const ribbonStroke = cut ? { ...sized, width: width * 2, align: "center", vertexWidths: stroke.vertexWidths?.map((w) => w * 2) } : sized;
800
+ const mesh = tessellateStroke(path, ribbonStroke, {
801
+ flattenTolerance: tolerance,
802
+ startInset: insets.start,
803
+ endInset: insets.end
804
+ });
805
+ let ribbon = trianglesToPath(mesh.vertices, mesh.indices);
806
+ if (cut) ribbon = align === "inner" ? pathIntersect(ribbon, path) : pathSubtract(ribbon, path);
807
+ const heads = [];
808
+ for (const { entry, path: head, size } of markerHeads(path, sized, width, tolerance)) {
809
+ if ((entry.fill ?? "line") !== "none") heads.push(head);
810
+ if (entry.outline && (entry.outline.paint ?? "line") !== "none") {
811
+ heads.push(strokeToPath(head, { width: entry.outline.width * size, cap: "round", join: "round" }, opts));
812
+ }
813
+ }
814
+ return heads.length === 0 ? ribbon : pathUnion(ribbon, ...heads);
815
+ }
816
+
817
+ export { coordsOutOfMetric, markerHeads, markerInset, markerKeyOf, markerReach, markerReadsKey, markerSites, metricKeepsRects, metricKey, polylineIntoMetric, polylineOutOfMetric, populatePolylineWidths, rectIntoMetric, resolveMarkerSize, resolveStrokeWidth, sameMetric, screenSpaceKey, splitForDash, strokeInsets, strokeSpaceOf, strokeToPath, tessellateStroke, trimInSpace, worldWidthInMetric, worldWidthInMetricAlongWorld };
818
+ //# sourceMappingURL=index.js.map
819
+ //# sourceMappingURL=index.js.map