@pixerate/editor 0.37.0 → 0.39.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/THIRD_PARTY_NOTICES.md +7 -0
- package/dist/canvas/index.cjs +43 -25
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +16 -3
- package/dist/canvas/index.d.ts +16 -3
- package/dist/canvas/index.js +19 -1
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-HPEKB352.js → chunk-3LHWTGHW.js} +803 -23
- package/dist/chunk-3LHWTGHW.js.map +1 -0
- package/dist/{chunk-GVB7ZPLZ.cjs → chunk-VNYRI265.cjs} +806 -26
- package/dist/chunk-VNYRI265.cjs.map +1 -0
- package/dist/{stroke-ysgaPpAo.d.ts → pathfinder-D98S2lV5.d.ts} +252 -1
- package/dist/{stroke-CWjFKal4.d.cts → pathfinder-DKRcXiLS.d.cts} +252 -1
- package/dist/vector/index.cjs +867 -220
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +285 -3
- package/dist/vector/index.d.ts +285 -3
- package/dist/vector/index.js +672 -25
- package/dist/vector/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-GVB7ZPLZ.cjs.map +0 -1
- package/dist/chunk-HPEKB352.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;// src/canvas/polygonBoolean.ts
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4;// src/canvas/polygonBoolean.ts
|
|
2
2
|
function ringSignedArea(ring) {
|
|
3
3
|
let sum = 0;
|
|
4
4
|
const n = ring.length;
|
|
@@ -675,14 +675,14 @@ function turnDeg(a, b, c) {
|
|
|
675
675
|
if (!u || !v) return 0;
|
|
676
676
|
return Math.acos(Math.min(1, Math.max(-1, dot(u, v)))) * 180 / Math.PI;
|
|
677
677
|
}
|
|
678
|
-
function findCorners(pts, closed, cornerAngle,
|
|
678
|
+
function findCorners(pts, closed, cornerAngle, span2) {
|
|
679
679
|
const n = pts.length;
|
|
680
680
|
const at = (i) => pts[closed ? (i % n + n) % n : i];
|
|
681
681
|
const reach = (i, step) => {
|
|
682
682
|
for (let k = 1; k < (closed ? n : Infinity); k++) {
|
|
683
683
|
const j = i + step * k;
|
|
684
684
|
if (!closed && (j < 0 || j >= n)) return k > 1 ? j - step : null;
|
|
685
|
-
if (dist(at(j), pts[i]) >=
|
|
685
|
+
if (dist(at(j), pts[i]) >= span2) return j;
|
|
686
686
|
}
|
|
687
687
|
return null;
|
|
688
688
|
};
|
|
@@ -704,7 +704,7 @@ function findCorners(pts, closed, cornerAngle, span) {
|
|
|
704
704
|
let sharpest = true;
|
|
705
705
|
for (let j = lo; j <= hi && sharpest; j++) {
|
|
706
706
|
const k = closed ? (j % n + n) % n : j;
|
|
707
|
-
if (k === i || dist(pts[k], pts[i]) >=
|
|
707
|
+
if (k === i || dist(pts[k], pts[i]) >= span2) continue;
|
|
708
708
|
if (turns[k] > turns[i] || turns[k] === turns[i] && k < i) sharpest = false;
|
|
709
709
|
}
|
|
710
710
|
if (sharpest) corners.push(i);
|
|
@@ -742,9 +742,9 @@ function fitCubics(points, tolerance, options = {}) {
|
|
|
742
742
|
const tol = Math.max(tolerance, 1e-6);
|
|
743
743
|
const cornerAngle = _nullishCoalesce(options.cornerAngleDeg, () => ( 30));
|
|
744
744
|
const n = pts.length;
|
|
745
|
-
const
|
|
745
|
+
const span2 = 4 * tol;
|
|
746
746
|
if (!closed || n < 3) {
|
|
747
|
-
const corners2 = [0, ...n > 2 ? findCorners(pts, false, cornerAngle,
|
|
747
|
+
const corners2 = [0, ...n > 2 ? findCorners(pts, false, cornerAngle, span2) : [], n - 1];
|
|
748
748
|
const out2 = [];
|
|
749
749
|
for (let k = 1; k < corners2.length; k++) {
|
|
750
750
|
const run = pts.slice(corners2[k - 1], corners2[k] + 1);
|
|
@@ -753,7 +753,7 @@ function fitCubics(points, tolerance, options = {}) {
|
|
|
753
753
|
if (closed && n === 2) out2.push(...fitCubicRun([pts[1], pts[0]], tol));
|
|
754
754
|
return out2;
|
|
755
755
|
}
|
|
756
|
-
const corners = findCorners(pts, true, cornerAngle,
|
|
756
|
+
const corners = findCorners(pts, true, cornerAngle, span2);
|
|
757
757
|
if (corners.length === 0) {
|
|
758
758
|
const ring = [...pts, pts[0]];
|
|
759
759
|
const t = _nullishCoalesce(unit(sub(pts[1], pts[n - 1])), () => ( [1, 0]));
|
|
@@ -2436,10 +2436,10 @@ function normalizeOperand(input) {
|
|
|
2436
2436
|
}
|
|
2437
2437
|
return { chains, fillRule };
|
|
2438
2438
|
}
|
|
2439
|
-
function scaleOf(
|
|
2439
|
+
function scaleOf(operandChains) {
|
|
2440
2440
|
let max = 0;
|
|
2441
|
-
for (const
|
|
2442
|
-
for (const chain of
|
|
2441
|
+
for (const chains of operandChains) {
|
|
2442
|
+
for (const chain of chains) {
|
|
2443
2443
|
for (const seg of chain) {
|
|
2444
2444
|
for (const [x, y] of seg) max = Math.max(max, Math.abs(x), Math.abs(y));
|
|
2445
2445
|
}
|
|
@@ -2845,15 +2845,14 @@ function splitPass2(pieces, pool, ctx) {
|
|
|
2845
2845
|
}
|
|
2846
2846
|
return { pieces: out, changed: true };
|
|
2847
2847
|
}
|
|
2848
|
-
function
|
|
2849
|
-
const
|
|
2850
|
-
const scale2 = scaleOf(operands);
|
|
2848
|
+
function splitChains(operandChains, options) {
|
|
2849
|
+
const scale2 = scaleOf(operandChains);
|
|
2851
2850
|
const eps = options.epsilon && options.epsilon > 0 ? options.epsilon : 1e-9 * scale2;
|
|
2852
2851
|
const pool = new VertexPool(eps);
|
|
2853
2852
|
const sources = [];
|
|
2854
2853
|
let pieces = [];
|
|
2855
|
-
|
|
2856
|
-
for (const chain of
|
|
2854
|
+
operandChains.forEach((chains, operand) => {
|
|
2855
|
+
for (const chain of chains) {
|
|
2857
2856
|
for (const raw of chain) {
|
|
2858
2857
|
const straight = raw.length > 2 && isStraightSegment(raw, eps * 0.01);
|
|
2859
2858
|
const seg = straight ? [raw[0], raw[raw.length - 1]] : raw;
|
|
@@ -2881,6 +2880,18 @@ function buildArrangement(inputs, options) {
|
|
|
2881
2880
|
}
|
|
2882
2881
|
}
|
|
2883
2882
|
if (!settled) throw new PathOpsError("intersections did not settle");
|
|
2883
|
+
return { pieces, pool, sources, eps, scale: scale2 };
|
|
2884
|
+
}
|
|
2885
|
+
function samePiece(e, pts, eps) {
|
|
2886
|
+
if (e.length === 2 && pts.length === 2) return true;
|
|
2887
|
+
return segmentNearest(e, segmentPoint(pts, 0.5)).distance <= eps * 4 && segmentNearest(pts, segmentPoint(e, 0.5)).distance <= eps * 4;
|
|
2888
|
+
}
|
|
2889
|
+
function buildArrangement(inputs, options) {
|
|
2890
|
+
const operands = inputs.map(normalizeOperand);
|
|
2891
|
+
const { pieces, sources, eps, scale: scale2 } = splitChains(
|
|
2892
|
+
operands.map((o) => o.chains),
|
|
2893
|
+
options
|
|
2894
|
+
);
|
|
2884
2895
|
const count = operands.length;
|
|
2885
2896
|
const groups = /* @__PURE__ */ new Map();
|
|
2886
2897
|
const edges = [];
|
|
@@ -2892,11 +2903,7 @@ function buildArrangement(inputs, options) {
|
|
|
2892
2903
|
const key = `${a.id},${b.id}`;
|
|
2893
2904
|
const group = _nullishCoalesce(groups.get(key), () => ( []));
|
|
2894
2905
|
if (!groups.has(key)) groups.set(key, group);
|
|
2895
|
-
|
|
2896
|
-
let edge = group.find((e) => {
|
|
2897
|
-
if (e.pts.length === 2 && pts.length === 2) return true;
|
|
2898
|
-
return segmentNearest(e.pts, mid).distance <= eps * 4 && segmentNearest(pts, segmentPoint(e.pts, 0.5)).distance <= eps * 4;
|
|
2899
|
-
});
|
|
2906
|
+
let edge = group.find((e) => samePiece(e.pts, pts, eps));
|
|
2900
2907
|
if (!edge) {
|
|
2901
2908
|
edge = {
|
|
2902
2909
|
a,
|
|
@@ -3289,6 +3296,63 @@ function nestedRingsToPath(rings, keep = () => true) {
|
|
|
3289
3296
|
});
|
|
3290
3297
|
return contoursToPath(contours);
|
|
3291
3298
|
}
|
|
3299
|
+
function planarSplit(inputs, options = {}) {
|
|
3300
|
+
for (const chains of inputs) {
|
|
3301
|
+
for (const chain of chains) {
|
|
3302
|
+
for (const seg of chain) for (const p of seg) if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) throw new PathOpsError(NON_FINITE);
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
const { pieces, pool, sources, eps, scale: scale2 } = splitChains(inputs, options);
|
|
3306
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3307
|
+
const edges = [];
|
|
3308
|
+
for (const piece of pieces) {
|
|
3309
|
+
const forward = piece.a.id < piece.b.id;
|
|
3310
|
+
const a = forward ? piece.a : piece.b;
|
|
3311
|
+
const b = forward ? piece.b : piece.a;
|
|
3312
|
+
const pts = forward ? piece.pts : [...piece.pts].reverse();
|
|
3313
|
+
const key = `${a.id},${b.id}`;
|
|
3314
|
+
let group = groups.get(key);
|
|
3315
|
+
if (!group) groups.set(key, group = []);
|
|
3316
|
+
const edge = group.find((e) => samePiece(e.pts, pts, eps));
|
|
3317
|
+
if (edge) {
|
|
3318
|
+
if (!edge.operands.includes(piece.operand)) edge.operands = [...edge.operands, piece.operand].sort((x, y) => x - y);
|
|
3319
|
+
continue;
|
|
3320
|
+
}
|
|
3321
|
+
const created = { a: a.id, b: b.id, pts, operands: [piece.operand], src: piece.src, sa: forward ? piece.s0 : piece.s1, sb: forward ? piece.s1 : piece.s0 };
|
|
3322
|
+
group.push(created);
|
|
3323
|
+
edges.push(created);
|
|
3324
|
+
}
|
|
3325
|
+
return { vertices: pool.vertices.map((v) => v.p), edges, sources: sources.map((s) => s.seg), eps, scale: scale2 };
|
|
3326
|
+
}
|
|
3327
|
+
function pathBooleanSelect(operands, selectors, options = {}) {
|
|
3328
|
+
if (operands.length === 0) return { parts: selectors.map(() => []), engine: "curve" };
|
|
3329
|
+
const covering = (flags) => flags.flatMap((f, k) => f ? [k] : []);
|
|
3330
|
+
try {
|
|
3331
|
+
const arr = buildArrangement(operands, options);
|
|
3332
|
+
const parts = selectors.map((select) => {
|
|
3333
|
+
const rings = extractRings(arr, (flags) => select(covering(flags)));
|
|
3334
|
+
return groupRings(rings).sort((a, b) => b[0].area - a[0].area).map(ringsToPath2);
|
|
3335
|
+
});
|
|
3336
|
+
return { parts, engine: "curve" };
|
|
3337
|
+
} catch (error) {
|
|
3338
|
+
const reason = reasonOf(error);
|
|
3339
|
+
if (reason === NON_FINITE) return { parts: selectors.map(() => []), engine: "curve", error: reason };
|
|
3340
|
+
if (options.fallback === false) throw error;
|
|
3341
|
+
_optionalChain([options, 'access', _31 => _31.onFallback, 'optionalCall', _32 => _32(reason)]);
|
|
3342
|
+
const tolerance = _nullishCoalesce(options.fallbackTolerance, () => ( 0.05));
|
|
3343
|
+
const pieces = dividePolygons(
|
|
3344
|
+
operands.map((o) => flattenOperand(o, tolerance)),
|
|
3345
|
+
{ epsilon: options.epsilon }
|
|
3346
|
+
);
|
|
3347
|
+
const parts = selectors.map((select) => {
|
|
3348
|
+
const picked = pieces.filter((p) => select(p.operands));
|
|
3349
|
+
if (!picked.length) return [];
|
|
3350
|
+
const multi = polygonBoolean("union", [picked.map((p) => p.polygon)], { epsilon: options.epsilon });
|
|
3351
|
+
return multi.map((polygon) => multiToPath([polygon], tolerance));
|
|
3352
|
+
});
|
|
3353
|
+
return { parts, engine: "polygon", fallbackReason: reason };
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3292
3356
|
|
|
3293
3357
|
// src/canvas/pathops/stroke.ts
|
|
3294
3358
|
var DEFAULT_TOLERANCE = 0.01;
|
|
@@ -3454,7 +3518,7 @@ function strokeContour(contour, h, style, tol) {
|
|
|
3454
3518
|
const all = contour.closed ? closeContour(contour) : contour.segments;
|
|
3455
3519
|
const segs = all.filter((s) => !isDegenerate(s, tiny));
|
|
3456
3520
|
if (segs.length === 0) {
|
|
3457
|
-
const p = _optionalChain([contour, 'access',
|
|
3521
|
+
const p = _optionalChain([contour, 'access', _33 => _33.segments, 'access', _34 => _34[0], 'optionalAccess', _35 => _35[0]]);
|
|
3458
3522
|
return p ? dotRings(p, [1, 0], h, style.cap) : [];
|
|
3459
3523
|
}
|
|
3460
3524
|
if (contour.closed) {
|
|
@@ -3587,11 +3651,11 @@ function deepPoint(rings) {
|
|
|
3587
3651
|
}
|
|
3588
3652
|
xs.sort((p, q) => p - q);
|
|
3589
3653
|
for (let i = 0; i + 1 < xs.length; i += 2) {
|
|
3590
|
-
const
|
|
3591
|
-
if (!best ||
|
|
3654
|
+
const span2 = xs[i + 1] - xs[i];
|
|
3655
|
+
if (!best || span2 > best.span) best = { span: span2, p: [(xs[i] + xs[i + 1]) / 2, y] };
|
|
3592
3656
|
}
|
|
3593
3657
|
}
|
|
3594
|
-
return _nullishCoalesce(_optionalChain([best, 'optionalAccess',
|
|
3658
|
+
return _nullishCoalesce(_optionalChain([best, 'optionalAccess', _36 => _36.p]), () => ( null));
|
|
3595
3659
|
}
|
|
3596
3660
|
function dropSpurious(rings, spurious) {
|
|
3597
3661
|
const children = rings.map(() => []);
|
|
@@ -3731,6 +3795,722 @@ function offsetPath(input, distance, options = {}) {
|
|
|
3731
3795
|
}
|
|
3732
3796
|
}
|
|
3733
3797
|
|
|
3798
|
+
// src/canvas/pathops/planar.ts
|
|
3799
|
+
var twin = (h) => h ^ 1;
|
|
3800
|
+
function boundsOf(segs) {
|
|
3801
|
+
let minX = Infinity;
|
|
3802
|
+
let minY = Infinity;
|
|
3803
|
+
let maxX = -Infinity;
|
|
3804
|
+
let maxY = -Infinity;
|
|
3805
|
+
for (const s of segs) {
|
|
3806
|
+
for (const [x, y] of s) {
|
|
3807
|
+
if (x < minX) minX = x;
|
|
3808
|
+
if (x > maxX) maxX = x;
|
|
3809
|
+
if (y < minY) minY = y;
|
|
3810
|
+
if (y > maxY) maxY = y;
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
return { minX, minY, maxX, maxY };
|
|
3814
|
+
}
|
|
3815
|
+
var inBounds = (b, p) => p[0] >= b.minX && p[0] <= b.maxX && p[1] >= b.minY && p[1] <= b.maxY;
|
|
3816
|
+
function withoutSpurs(cycle) {
|
|
3817
|
+
const out = [];
|
|
3818
|
+
for (const h of cycle) {
|
|
3819
|
+
if (out.length && out[out.length - 1] === twin(h)) out.pop();
|
|
3820
|
+
else out.push(h);
|
|
3821
|
+
}
|
|
3822
|
+
while (out.length >= 2 && twin(out[0]) === out[out.length - 1]) {
|
|
3823
|
+
out.pop();
|
|
3824
|
+
out.shift();
|
|
3825
|
+
}
|
|
3826
|
+
return out;
|
|
3827
|
+
}
|
|
3828
|
+
function pinEnds(pts, a, b) {
|
|
3829
|
+
const n = pts.length;
|
|
3830
|
+
if (n === 2) return [a, b];
|
|
3831
|
+
const out = pts.map((p) => [p[0], p[1]]);
|
|
3832
|
+
const d0 = [a[0] - pts[0][0], a[1] - pts[0][1]];
|
|
3833
|
+
const d1 = [b[0] - pts[n - 1][0], b[1] - pts[n - 1][1]];
|
|
3834
|
+
out[0] = a;
|
|
3835
|
+
out[n - 1] = b;
|
|
3836
|
+
if (n === 3) {
|
|
3837
|
+
out[1] = [pts[1][0] + (d0[0] + d1[0]) / 2, pts[1][1] + (d0[1] + d1[1]) / 2];
|
|
3838
|
+
} else {
|
|
3839
|
+
out[1] = [pts[1][0] + d0[0], pts[1][1] + d0[1]];
|
|
3840
|
+
out[2] = [pts[2][0] + d1[0], pts[2][1] + d1[1]];
|
|
3841
|
+
}
|
|
3842
|
+
return out;
|
|
3843
|
+
}
|
|
3844
|
+
function interiorPoint(path) {
|
|
3845
|
+
const chains = Array.isArray(path) && path.length > 0 && Array.isArray(path[0]) && Array.isArray(_optionalChain([path, 'access', _37 => _37[0], 'access', _38 => _38[0], 'optionalAccess', _39 => _39[0]])) ? path : pathToContours(path).map((c) => closeContour(c, true));
|
|
3846
|
+
const b = boundsOf(chains.flat());
|
|
3847
|
+
if (!(b.maxX > b.minX) || !(b.maxY > b.minY)) return null;
|
|
3848
|
+
const tol = Math.max(b.maxX - b.minX, b.maxY - b.minY) * 1e-3;
|
|
3849
|
+
const rings = chains.filter((c) => c.length).map((c) => flattenChain(c, tol));
|
|
3850
|
+
let best = null;
|
|
3851
|
+
for (const f of [0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, 0.0625, 0.9375]) {
|
|
3852
|
+
const y = b.minY + (b.maxY - b.minY) * (f + 61803e-7);
|
|
3853
|
+
const xs = [];
|
|
3854
|
+
for (const ring of rings) {
|
|
3855
|
+
for (let i = 0; i < ring.length; i++) {
|
|
3856
|
+
const a = ring[i];
|
|
3857
|
+
const c = ring[(i + 1) % ring.length];
|
|
3858
|
+
if (a[1] <= y !== c[1] <= y) xs.push(a[0] + (y - a[1]) / (c[1] - a[1]) * (c[0] - a[0]));
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
xs.sort((p, q) => p - q);
|
|
3862
|
+
for (let i = 0; i + 1 < xs.length; i++) {
|
|
3863
|
+
const width = xs[i + 1] - xs[i];
|
|
3864
|
+
if (!(width > (_nullishCoalesce(_optionalChain([best, 'optionalAccess', _40 => _40.width]), () => ( 0))))) continue;
|
|
3865
|
+
const m = [(xs[i] + xs[i + 1]) / 2, y];
|
|
3866
|
+
if (windingNumber(chains, m) !== 0) best = { width, p: m };
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
return _nullishCoalesce(_optionalChain([best, 'optionalAccess', _41 => _41.p]), () => ( null));
|
|
3870
|
+
}
|
|
3871
|
+
var PlanarMap = (_class4 = class _PlanarMap {
|
|
3872
|
+
constructor(vertices, edges, sources, eps, inputCount, inputs) {;_class4.prototype.__init6.call(this);_class4.prototype.__init7.call(this);
|
|
3873
|
+
this.vertices = vertices;
|
|
3874
|
+
this.edges = edges;
|
|
3875
|
+
this.sources = sources;
|
|
3876
|
+
this.eps = eps;
|
|
3877
|
+
this.inputCount = inputCount;
|
|
3878
|
+
this.fills = inputs.map((input) => {
|
|
3879
|
+
const chains = input.fills === false ? [] : pathToContours(input.path).filter((c) => c.closed).map((c) => closeContour(c));
|
|
3880
|
+
return { chains, fillRule: _nullishCoalesce(input.fillRule, () => ( "nonzero")), bounds: boundsOf(chains.flat()) };
|
|
3881
|
+
});
|
|
3882
|
+
const nv = vertices.length;
|
|
3883
|
+
this.around = Array.from({ length: nv }, () => []);
|
|
3884
|
+
for (let h = 0; h < edges.length * 2; h++) this.around[this.origin(h)].push(h);
|
|
3885
|
+
this.slot = new Array(edges.length * 2).fill(0);
|
|
3886
|
+
this.around.forEach((list, v) => {
|
|
3887
|
+
if (list.length === 0) return;
|
|
3888
|
+
const at = vertices[v];
|
|
3889
|
+
let r = Infinity;
|
|
3890
|
+
for (const h of list) r = Math.min(r, dist2(at, vertices[this.origin(twin(h))]));
|
|
3891
|
+
r *= 0.5;
|
|
3892
|
+
const keyed = list.map((h) => ({ h, angle: this.leavingAngle(h, at, r) }));
|
|
3893
|
+
keyed.sort((a, b) => a.angle - b.angle);
|
|
3894
|
+
list.length = 0;
|
|
3895
|
+
keyed.forEach(({ h }, k) => {
|
|
3896
|
+
list.push(h);
|
|
3897
|
+
this.slot[h] = k;
|
|
3898
|
+
});
|
|
3899
|
+
});
|
|
3900
|
+
const parent = Array.from({ length: nv }, (_, i) => i);
|
|
3901
|
+
const find = (v) => {
|
|
3902
|
+
while (parent[v] !== v) {
|
|
3903
|
+
parent[v] = parent[parent[v]];
|
|
3904
|
+
v = parent[v];
|
|
3905
|
+
}
|
|
3906
|
+
return v;
|
|
3907
|
+
};
|
|
3908
|
+
for (const e of edges) parent[find(e.a)] = find(e.b);
|
|
3909
|
+
this.component = Array.from({ length: nv }, (_, v) => find(v));
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
|
|
3913
|
+
|
|
3914
|
+
|
|
3915
|
+
|
|
3916
|
+
|
|
3917
|
+
|
|
3918
|
+
|
|
3919
|
+
__init6() {this.cyclesCache = null}
|
|
3920
|
+
__init7() {this.sourcesCache = []}
|
|
3921
|
+
|
|
3922
|
+
/**
|
|
3923
|
+
* Maps `inputs` (their subpaths as given: open ones stay open). Null when they cannot be mapped
|
|
3924
|
+
* (non-finite points, more than `maxSegments` segments or an intersection search that does
|
|
3925
|
+
* not settle) or have no edges.
|
|
3926
|
+
*/
|
|
3927
|
+
static build(inputs, options = {}) {
|
|
3928
|
+
const chains = inputs.map((input) => pathToContours(input.path).map((c) => closeContour(c)).filter((c) => c.length));
|
|
3929
|
+
const count = chains.reduce((n, cs) => n + cs.reduce((m, c) => m + c.length, 0), 0);
|
|
3930
|
+
if (count === 0 || count > (_nullishCoalesce(options.maxSegments, () => ( 4096)))) return null;
|
|
3931
|
+
let split;
|
|
3932
|
+
try {
|
|
3933
|
+
split = planarSplit(chains, options);
|
|
3934
|
+
} catch (e6) {
|
|
3935
|
+
return null;
|
|
3936
|
+
}
|
|
3937
|
+
const edges = split.edges.filter((e) => e.a !== e.b).map((e) => ({ ...e, source: _nullishCoalesce(e.operands[e.operands.length - 1], () => ( -1)) }));
|
|
3938
|
+
if (!edges.length) return null;
|
|
3939
|
+
const gap = _nullishCoalesce(options.gap, () => ( 0));
|
|
3940
|
+
if (gap > split.eps) closeGaps(split.vertices, edges, gap);
|
|
3941
|
+
return new _PlanarMap(split.vertices, edges, split.sources, split.eps, inputs.length, inputs);
|
|
3942
|
+
}
|
|
3943
|
+
// -- half-edges ------------------------------------------------------------
|
|
3944
|
+
origin(h) {
|
|
3945
|
+
const e = this.edges[h >> 1];
|
|
3946
|
+
return h & 1 ? e.b : e.a;
|
|
3947
|
+
}
|
|
3948
|
+
halfPts(h) {
|
|
3949
|
+
const pts = this.edges[h >> 1].pts;
|
|
3950
|
+
return h & 1 ? [...pts].reverse() : pts;
|
|
3951
|
+
}
|
|
3952
|
+
/** The half-edge after `h` on the face to its left: the sharpest left turn at its end. */
|
|
3953
|
+
next(h) {
|
|
3954
|
+
const back = twin(h);
|
|
3955
|
+
const list = this.around[this.origin(back)];
|
|
3956
|
+
return list[(this.slot[back] + list.length - 1) % list.length];
|
|
3957
|
+
}
|
|
3958
|
+
/** Direction (angle) in which half-edge `h`, leaving `at`, first gets `r` away from it. */
|
|
3959
|
+
leavingAngle(h, at, r) {
|
|
3960
|
+
const pts = this.halfPts(h);
|
|
3961
|
+
const d = (t2) => dist2(segmentPoint(pts, t2), at);
|
|
3962
|
+
let lo = 0;
|
|
3963
|
+
let hi = 1;
|
|
3964
|
+
const steps = pts.length === 2 ? 1 : 16;
|
|
3965
|
+
for (let k = 1; k <= steps; k++) {
|
|
3966
|
+
if (d(k / steps) >= r) {
|
|
3967
|
+
hi = k / steps;
|
|
3968
|
+
break;
|
|
3969
|
+
}
|
|
3970
|
+
lo = k / steps;
|
|
3971
|
+
}
|
|
3972
|
+
for (let i = 0; i < 40 && hi - lo > 1e-12; i++) {
|
|
3973
|
+
const m = (lo + hi) / 2;
|
|
3974
|
+
if (d(m) >= r) hi = m;
|
|
3975
|
+
else lo = m;
|
|
3976
|
+
}
|
|
3977
|
+
const p = segmentPoint(pts, hi);
|
|
3978
|
+
if (dist2(p, at) > 0) return Math.atan2(p[1] - at[1], p[0] - at[0]);
|
|
3979
|
+
const t = segmentTangent(pts, 0);
|
|
3980
|
+
return Math.atan2(t[1], t[0]);
|
|
3981
|
+
}
|
|
3982
|
+
/** Segments of a run of half-edges (a loop unless `closed` is false), pieces of one input segment joined again. */
|
|
3983
|
+
cycleSegments(halves, closed = true) {
|
|
3984
|
+
const runs = halves.map((h) => {
|
|
3985
|
+
const e = this.edges[h >> 1];
|
|
3986
|
+
return { h, src: e.source < 0 ? -1 : e.src, from: h & 1 ? e.sb : e.sa, to: h & 1 ? e.sa : e.sb };
|
|
3987
|
+
});
|
|
3988
|
+
const joins = (a, b) => a.src >= 0 && a.src === b.src && Math.abs(a.to - b.from) < 1e-9 && a.to - a.from > 0 === b.to - b.from > 0;
|
|
3989
|
+
const n = runs.length;
|
|
3990
|
+
let start = 0;
|
|
3991
|
+
for (let k = 0; closed && k < n; k++) {
|
|
3992
|
+
if (!joins(runs[(k + n - 1) % n], runs[k])) {
|
|
3993
|
+
start = k;
|
|
3994
|
+
break;
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
const ordered = [...runs.slice(start), ...runs.slice(0, start)];
|
|
3998
|
+
const out = [];
|
|
3999
|
+
let i = 0;
|
|
4000
|
+
while (i < ordered.length) {
|
|
4001
|
+
let j = i + 1;
|
|
4002
|
+
while (j < ordered.length && joins(ordered[j - 1], ordered[j])) j++;
|
|
4003
|
+
const first = ordered[i];
|
|
4004
|
+
const last = ordered[j - 1];
|
|
4005
|
+
if (j - i === 1) out.push(this.halfPts(first.h));
|
|
4006
|
+
else {
|
|
4007
|
+
const a = this.vertices[this.origin(first.h)];
|
|
4008
|
+
const b = this.vertices[this.origin(twin(last.h))];
|
|
4009
|
+
const seg = this.sources[first.src];
|
|
4010
|
+
out.push(seg.length === 2 ? [a, b] : pinEnds(subSegment(seg, first.from, last.to), a, b));
|
|
4011
|
+
}
|
|
4012
|
+
i = j;
|
|
4013
|
+
}
|
|
4014
|
+
return out;
|
|
4015
|
+
}
|
|
4016
|
+
cycles() {
|
|
4017
|
+
if (this.cyclesCache) return this.cyclesCache;
|
|
4018
|
+
const halves = this.edges.length * 2;
|
|
4019
|
+
const seen = new Uint8Array(halves);
|
|
4020
|
+
const faces = [];
|
|
4021
|
+
const holes = [];
|
|
4022
|
+
const sliver = this.eps * 10;
|
|
4023
|
+
for (let start = 0; start < halves; start++) {
|
|
4024
|
+
if (seen[start]) continue;
|
|
4025
|
+
const walk = [];
|
|
4026
|
+
let h = start;
|
|
4027
|
+
while (!seen[h]) {
|
|
4028
|
+
seen[h] = 1;
|
|
4029
|
+
walk.push(h);
|
|
4030
|
+
h = this.next(h);
|
|
4031
|
+
}
|
|
4032
|
+
const cycle = withoutSpurs(walk);
|
|
4033
|
+
if (!cycle.length) continue;
|
|
4034
|
+
const segments = this.cycleSegments(cycle);
|
|
4035
|
+
const area = contourSignedArea(segments);
|
|
4036
|
+
let perimeter = 0;
|
|
4037
|
+
for (const s of segments) perimeter += s.length === 2 ? dist2(s[0], s[1]) : segmentLength(s, 0, 1, 1e-6);
|
|
4038
|
+
if (!(perimeter > 0)) continue;
|
|
4039
|
+
const entry = { halves: cycle, segments, area, bounds: boundsOf(segments) };
|
|
4040
|
+
if (area > 0 && 2 * area / perimeter >= sliver) faces.push(entry);
|
|
4041
|
+
else if (area < 0 && -2 * area / perimeter >= sliver) holes.push(entry);
|
|
4042
|
+
}
|
|
4043
|
+
const faceHoles = faces.map(() => []);
|
|
4044
|
+
holes.forEach((hole, k) => {
|
|
4045
|
+
const v = this.origin(hole.halves[0]);
|
|
4046
|
+
const p = this.vertices[v];
|
|
4047
|
+
const own = this.component[v];
|
|
4048
|
+
let best = -1;
|
|
4049
|
+
faces.forEach((face, i) => {
|
|
4050
|
+
if (this.component[this.origin(face.halves[0])] === own || !inBounds(face.bounds, p)) return;
|
|
4051
|
+
if (best >= 0 && faces[best].area <= face.area) return;
|
|
4052
|
+
if (windingNumber([face.segments], p) !== 0) best = i;
|
|
4053
|
+
});
|
|
4054
|
+
if (best >= 0) faceHoles[best].push(k);
|
|
4055
|
+
});
|
|
4056
|
+
const faceOf = new Int32Array(halves).fill(-1);
|
|
4057
|
+
faces.forEach((face, i) => {
|
|
4058
|
+
for (const h of face.halves) faceOf[h] = i;
|
|
4059
|
+
for (const k of faceHoles[i]) for (const h of holes[k].halves) faceOf[h] = i;
|
|
4060
|
+
});
|
|
4061
|
+
this.cyclesCache = { faces, holes, faceHoles, faceOf };
|
|
4062
|
+
this.sourcesCache = faces.map(() => null);
|
|
4063
|
+
return this.cyclesCache;
|
|
4064
|
+
}
|
|
4065
|
+
faceChains(i) {
|
|
4066
|
+
const { faces, holes, faceHoles } = this.cycles();
|
|
4067
|
+
return [faces[i].segments, ...faceHoles[i].map((k) => holes[k].segments)];
|
|
4068
|
+
}
|
|
4069
|
+
/** The bounded faces of the map. */
|
|
4070
|
+
get faces() {
|
|
4071
|
+
const { faces, holes, faceHoles } = this.cycles();
|
|
4072
|
+
return faces.map((f, i) => {
|
|
4073
|
+
const chains = this.faceChains(i);
|
|
4074
|
+
const area = f.area + faceHoles[i].reduce((sum, k) => sum + holes[k].area, 0);
|
|
4075
|
+
return { path: contoursToPath(chains.map((segments) => ({ segments, closed: true }))), area, bounds: f.bounds };
|
|
4076
|
+
});
|
|
4077
|
+
}
|
|
4078
|
+
/** The inputs whose fill covers face `i`, ascending (empty for an area only outlines enclose). */
|
|
4079
|
+
faceSources(i) {
|
|
4080
|
+
this.cycles();
|
|
4081
|
+
const cached = this.sourcesCache[i];
|
|
4082
|
+
if (cached) return cached;
|
|
4083
|
+
const p = interiorPoint(this.faceChains(i));
|
|
4084
|
+
const out = [];
|
|
4085
|
+
if (p) {
|
|
4086
|
+
this.fills.forEach((fill, k) => {
|
|
4087
|
+
if (!fill.chains.length || !inBounds(fill.bounds, p)) return;
|
|
4088
|
+
const w = windingNumber(fill.chains, p);
|
|
4089
|
+
if (fill.fillRule === "evenodd" ? (w & 1) === 1 : w !== 0) out.push(k);
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
4092
|
+
this.sourcesCache[i] = out;
|
|
4093
|
+
return out;
|
|
4094
|
+
}
|
|
4095
|
+
/** A point well inside face `i`. */
|
|
4096
|
+
faceInterior(i) {
|
|
4097
|
+
return interiorPoint(this.faceChains(i));
|
|
4098
|
+
}
|
|
4099
|
+
/** The face under `p` (the smallest one around it), or -1. */
|
|
4100
|
+
faceAt(p) {
|
|
4101
|
+
const { faces } = this.cycles();
|
|
4102
|
+
let best = -1;
|
|
4103
|
+
faces.forEach((face, i) => {
|
|
4104
|
+
if (!inBounds(face.bounds, p) || best >= 0 && faces[best].area <= face.area) return;
|
|
4105
|
+
if (windingNumber(this.faceChains(i), p) !== 0) best = i;
|
|
4106
|
+
});
|
|
4107
|
+
return best;
|
|
4108
|
+
}
|
|
4109
|
+
/**
|
|
4110
|
+
* The union of faces, traced along the map's own edges: those between two merged faces drop
|
|
4111
|
+
* out exactly, so no seam or sliver is left where a boolean union of their outlines could
|
|
4112
|
+
* round shared corners apart. Outer subpaths come out counter-clockwise (y-up), holes clockwise.
|
|
4113
|
+
*/
|
|
4114
|
+
merge(faceIndices) {
|
|
4115
|
+
const { faceOf } = this.cycles();
|
|
4116
|
+
const picked = new Set(faceIndices);
|
|
4117
|
+
const merged = (h) => faceOf[h] >= 0 && picked.has(faceOf[h]);
|
|
4118
|
+
const boundary = (h) => merged(h) && !merged(twin(h));
|
|
4119
|
+
const used = new Uint8Array(this.edges.length * 2);
|
|
4120
|
+
const contours = [];
|
|
4121
|
+
for (let start = 0; start < used.length; start++) {
|
|
4122
|
+
if (used[start] || !boundary(start)) continue;
|
|
4123
|
+
const cycle = [];
|
|
4124
|
+
let h = start;
|
|
4125
|
+
let ok = true;
|
|
4126
|
+
while (!used[h]) {
|
|
4127
|
+
used[h] = 1;
|
|
4128
|
+
cycle.push(h);
|
|
4129
|
+
const back = twin(h);
|
|
4130
|
+
const list = this.around[this.origin(back)];
|
|
4131
|
+
const k = this.slot[back];
|
|
4132
|
+
const n = list.length;
|
|
4133
|
+
let found = -1;
|
|
4134
|
+
for (let j = 1; j <= n; j++) {
|
|
4135
|
+
const g = list[(k - j + n * 2) % n];
|
|
4136
|
+
if (boundary(g)) {
|
|
4137
|
+
found = g;
|
|
4138
|
+
break;
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
if (found < 0) {
|
|
4142
|
+
ok = false;
|
|
4143
|
+
break;
|
|
4144
|
+
}
|
|
4145
|
+
h = found;
|
|
4146
|
+
}
|
|
4147
|
+
if (!ok || h !== start) continue;
|
|
4148
|
+
contours.push({ segments: this.cycleSegments(cycle), closed: true });
|
|
4149
|
+
}
|
|
4150
|
+
return contoursToPath(contours);
|
|
4151
|
+
}
|
|
4152
|
+
/**
|
|
4153
|
+
* The edges joined into runs between junctions: vertices where other than two edges meet, or
|
|
4154
|
+
* where the two edges lie on different inputs. Each run is keyed by its input.
|
|
4155
|
+
*/
|
|
4156
|
+
chains() {
|
|
4157
|
+
const passes = (v) => {
|
|
4158
|
+
const list = this.around[v];
|
|
4159
|
+
if (list.length !== 2) return null;
|
|
4160
|
+
return this.edges[list[0] >> 1].source === this.edges[list[1] >> 1].source ? [list[0], list[1]] : null;
|
|
4161
|
+
};
|
|
4162
|
+
const used = new Uint8Array(this.edges.length);
|
|
4163
|
+
const out = [];
|
|
4164
|
+
const walk = (h0) => {
|
|
4165
|
+
const source = this.edges[h0 >> 1].source;
|
|
4166
|
+
const halves = [];
|
|
4167
|
+
let h = h0;
|
|
4168
|
+
let closed = false;
|
|
4169
|
+
for (; ; ) {
|
|
4170
|
+
if (used[h >> 1]) {
|
|
4171
|
+
closed = true;
|
|
4172
|
+
break;
|
|
4173
|
+
}
|
|
4174
|
+
used[h >> 1] = 1;
|
|
4175
|
+
halves.push(h);
|
|
4176
|
+
const v = this.origin(twin(h));
|
|
4177
|
+
const pass = passes(v);
|
|
4178
|
+
if (!pass) {
|
|
4179
|
+
closed = v === this.origin(h0);
|
|
4180
|
+
break;
|
|
4181
|
+
}
|
|
4182
|
+
h = pass[0] === twin(h) ? pass[1] : pass[0];
|
|
4183
|
+
}
|
|
4184
|
+
if (source < 0) return;
|
|
4185
|
+
const segments = this.cycleSegments(halves, closed);
|
|
4186
|
+
out.push({ segments, closed, source });
|
|
4187
|
+
};
|
|
4188
|
+
this.around.forEach((list, v) => {
|
|
4189
|
+
if (passes(v)) return;
|
|
4190
|
+
for (const h of list) if (!used[h >> 1]) walk(h);
|
|
4191
|
+
});
|
|
4192
|
+
for (let e = 0; e < this.edges.length; e++) if (!used[e]) walk(e * 2);
|
|
4193
|
+
return out;
|
|
4194
|
+
}
|
|
4195
|
+
// -- fill regions (cadcraft `boundary_at_gap`) -------------------------------
|
|
4196
|
+
/**
|
|
4197
|
+
* The region enclosing `p`, traced as cadcraft does it: a ray from `p` towards +x meets the
|
|
4198
|
+
* edges in order; for each, the half-edge with `p` on its left is followed by always taking the
|
|
4199
|
+
* sharpest left turn, until a loop that runs counter-clockwise round `p` comes back. The pieces
|
|
4200
|
+
* of the map nested inside that loop (islands) become its holes. Null when nothing encloses `p`.
|
|
4201
|
+
*/
|
|
4202
|
+
regionAt(p) {
|
|
4203
|
+
const { faces, faceOf } = this.cycles();
|
|
4204
|
+
const hits = [];
|
|
4205
|
+
this.edges.forEach((e, i) => {
|
|
4206
|
+
const ay = this.vertices[e.a][1];
|
|
4207
|
+
const by = this.vertices[e.b][1];
|
|
4208
|
+
if (ay > p[1] === by > p[1]) return;
|
|
4209
|
+
const t = solveMonotone(e.pts, 1, p[1]);
|
|
4210
|
+
const x = segmentPoint(e.pts, t)[0];
|
|
4211
|
+
if (x > p[0]) hits.push({ x, e: i });
|
|
4212
|
+
});
|
|
4213
|
+
hits.sort((a, b) => a.x - b.x);
|
|
4214
|
+
for (const { e } of hits.slice(0, 64)) {
|
|
4215
|
+
const ay = this.vertices[this.edges[e].a][1];
|
|
4216
|
+
const by = this.vertices[this.edges[e].b][1];
|
|
4217
|
+
const h = by > ay ? e * 2 : e * 2 + 1;
|
|
4218
|
+
const walk = [];
|
|
4219
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4220
|
+
let g = h;
|
|
4221
|
+
while (!seen.has(g)) {
|
|
4222
|
+
seen.add(g);
|
|
4223
|
+
walk.push(g);
|
|
4224
|
+
g = this.next(g);
|
|
4225
|
+
}
|
|
4226
|
+
if (g !== h) continue;
|
|
4227
|
+
const cycle = withoutSpurs(walk);
|
|
4228
|
+
if (!cycle.length) continue;
|
|
4229
|
+
const segments = this.cycleSegments(cycle);
|
|
4230
|
+
if (!(contourSignedArea(segments) > 0) || windingNumber([segments], p) === 0) continue;
|
|
4231
|
+
const face = faceOf[cycle[0]];
|
|
4232
|
+
if (face >= 0 && faces[face].halves.includes(cycle[0])) {
|
|
4233
|
+
return contoursToPath(this.faceChains(face).map((s) => ({ segments: s, closed: true })));
|
|
4234
|
+
}
|
|
4235
|
+
return contoursToPath([{ segments, closed: true }]);
|
|
4236
|
+
}
|
|
4237
|
+
return null;
|
|
4238
|
+
}
|
|
4239
|
+
}, _class4);
|
|
4240
|
+
function closeGaps(vertices, edges, gap) {
|
|
4241
|
+
const degree = new Array(vertices.length).fill(0);
|
|
4242
|
+
const linked = /* @__PURE__ */ new Set();
|
|
4243
|
+
for (const e of edges) {
|
|
4244
|
+
degree[e.a]++;
|
|
4245
|
+
degree[e.b]++;
|
|
4246
|
+
linked.add(`${Math.min(e.a, e.b)},${Math.max(e.a, e.b)}`);
|
|
4247
|
+
}
|
|
4248
|
+
const ends = degree.flatMap((d, v) => d === 1 ? [v] : []).sort((a, b) => vertices[a][0] - vertices[b][0]);
|
|
4249
|
+
const joined = /* @__PURE__ */ new Set();
|
|
4250
|
+
let lo = 0;
|
|
4251
|
+
for (let i = 0; i < ends.length; i++) {
|
|
4252
|
+
const u = ends[i];
|
|
4253
|
+
if (joined.has(u)) continue;
|
|
4254
|
+
const pu = vertices[u];
|
|
4255
|
+
while (lo < ends.length && vertices[ends[lo]][0] < pu[0] - gap) lo++;
|
|
4256
|
+
let best = -1;
|
|
4257
|
+
let bestD = Infinity;
|
|
4258
|
+
for (let j = lo; j < ends.length; j++) {
|
|
4259
|
+
const v = ends[j];
|
|
4260
|
+
const pv = vertices[v];
|
|
4261
|
+
if (pv[0] > pu[0] + gap) break;
|
|
4262
|
+
if (v === u || joined.has(v) || linked.has(`${Math.min(u, v)},${Math.max(u, v)}`)) continue;
|
|
4263
|
+
const d = dist2(pu, pv);
|
|
4264
|
+
if (d <= gap && d < bestD) {
|
|
4265
|
+
best = v;
|
|
4266
|
+
bestD = d;
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
if (best < 0) continue;
|
|
4270
|
+
const a = Math.min(u, best);
|
|
4271
|
+
const b = Math.max(u, best);
|
|
4272
|
+
edges.push({ a, b, pts: [vertices[a], vertices[b]], operands: [], src: -1, sa: 0, sb: 1, source: -1 });
|
|
4273
|
+
joined.add(u);
|
|
4274
|
+
joined.add(best);
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
function contourEnclosesArea(contour) {
|
|
4278
|
+
const segs = closeContour(contour, true);
|
|
4279
|
+
if (!segs.length) return false;
|
|
4280
|
+
const b = boundsOf(segs);
|
|
4281
|
+
const w = b.maxX - b.minX;
|
|
4282
|
+
const h = b.maxY - b.minY;
|
|
4283
|
+
return Math.abs(contourSignedArea(segs)) > 1e-9 * (w * w + h * h);
|
|
4284
|
+
}
|
|
4285
|
+
function nearestU(segs, p) {
|
|
4286
|
+
let best = null;
|
|
4287
|
+
segs.forEach((seg, k) => {
|
|
4288
|
+
const n = segmentNearest(seg, p);
|
|
4289
|
+
if (!best || n.distance < best.d) best = { u: k + n.t, d: n.distance };
|
|
4290
|
+
});
|
|
4291
|
+
return best;
|
|
4292
|
+
}
|
|
4293
|
+
function span(segs, u0, u1) {
|
|
4294
|
+
const n = segs.length;
|
|
4295
|
+
if (!n || !Number.isFinite(u0) || !Number.isFinite(u1)) return [];
|
|
4296
|
+
const first = Math.max(0, Math.floor(u0));
|
|
4297
|
+
const last = Math.min(Math.max(0, Math.ceil(u1)), first + 2 * n);
|
|
4298
|
+
const out = [];
|
|
4299
|
+
for (let k = first; k < last; k++) {
|
|
4300
|
+
const t0 = Math.min(1, Math.max(0, u0 - k));
|
|
4301
|
+
const t1 = Math.min(1, Math.max(0, u1 - k));
|
|
4302
|
+
if (t1 - t0 > 1e-12) out.push(subSegment(segs[k % n], t0, t1));
|
|
4303
|
+
}
|
|
4304
|
+
return out;
|
|
4305
|
+
}
|
|
4306
|
+
function snapEnds2(segs, to, tol) {
|
|
4307
|
+
if (!segs.length) return segs;
|
|
4308
|
+
const out = segs.map((s) => [...s]);
|
|
4309
|
+
const move = (seg, end, handle) => {
|
|
4310
|
+
const q = to.find((t) => dist2(t, seg[end]) <= tol);
|
|
4311
|
+
if (!q) return;
|
|
4312
|
+
const d = [q[0] - seg[end][0], q[1] - seg[end][1]];
|
|
4313
|
+
seg[end] = q;
|
|
4314
|
+
if (seg.length === 4) seg[handle] = [seg[handle][0] + d[0], seg[handle][1] + d[1]];
|
|
4315
|
+
};
|
|
4316
|
+
move(out[0], 0, 1);
|
|
4317
|
+
const last = out[out.length - 1];
|
|
4318
|
+
move(last, last.length - 1, last.length - 2);
|
|
4319
|
+
return out;
|
|
4320
|
+
}
|
|
4321
|
+
function cutOutPiece(path, piece, fillCloses, tol) {
|
|
4322
|
+
const segs = piece.segments;
|
|
4323
|
+
if (!segs.length) return null;
|
|
4324
|
+
let mid = null;
|
|
4325
|
+
let longest = -1;
|
|
4326
|
+
let perimeter = 0;
|
|
4327
|
+
for (const seg of segs) {
|
|
4328
|
+
const len2 = segmentLength(seg);
|
|
4329
|
+
perimeter += len2;
|
|
4330
|
+
if (len2 > longest) {
|
|
4331
|
+
longest = len2;
|
|
4332
|
+
mid = segmentPoint(seg, 0.5);
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
if (!mid || perimeter <= tol) return null;
|
|
4336
|
+
const a = segs[0][0];
|
|
4337
|
+
const lastSeg = segs[segs.length - 1];
|
|
4338
|
+
const b = lastSeg[lastSeg.length - 1];
|
|
4339
|
+
const whole = !!piece.closed || dist2(a, b) <= tol;
|
|
4340
|
+
const contours = pathToContours(path);
|
|
4341
|
+
let found = null;
|
|
4342
|
+
contours.forEach((c, index2) => {
|
|
4343
|
+
if (!c.segments.length) return;
|
|
4344
|
+
const closed2 = c.closed || fillCloses && contourEnclosesArea(c);
|
|
4345
|
+
const ring2 = closeContour(c, closed2);
|
|
4346
|
+
const n = nearestU(ring2, mid);
|
|
4347
|
+
if (n && n.d <= tol && (!found || n.d < found.d)) found = { index: index2, ring: ring2, closed: closed2, um: n.u, d: n.d };
|
|
4348
|
+
});
|
|
4349
|
+
if (!found) return null;
|
|
4350
|
+
const { index, ring, closed, um } = found;
|
|
4351
|
+
const len = ring.length;
|
|
4352
|
+
const real = closeContour(contours[index]).length;
|
|
4353
|
+
const at = (p) => {
|
|
4354
|
+
const n = nearestU(ring, p);
|
|
4355
|
+
return n && n.d <= tol ? n.u : null;
|
|
4356
|
+
};
|
|
4357
|
+
let keep = [];
|
|
4358
|
+
if (!whole) {
|
|
4359
|
+
const ua = at(a);
|
|
4360
|
+
const ub = at(b);
|
|
4361
|
+
if (ua === null || ub === null) return null;
|
|
4362
|
+
if (closed) {
|
|
4363
|
+
const fwd = (from, to) => ((to - from) % len + len) % len;
|
|
4364
|
+
const [start, end] = fwd(ua, um) <= fwd(ua, ub) ? [ub, ua] : [ua, ub];
|
|
4365
|
+
const rest = fwd(start, end);
|
|
4366
|
+
if (rest > 1e-9 && len - rest > 1e-9) {
|
|
4367
|
+
const parts = real >= len ? [[0, 2 * len]] : [[0, real], [len, len + real]];
|
|
4368
|
+
keep = parts.map(([lo, hi]) => [Math.max(start, lo), Math.min(start + rest, hi)]).filter(([x, y]) => y - x > 1e-9);
|
|
4369
|
+
}
|
|
4370
|
+
} else {
|
|
4371
|
+
const lo = Math.min(ua, ub);
|
|
4372
|
+
const hi = Math.max(ua, ub);
|
|
4373
|
+
if (um < lo - 1e-9 || um > hi + 1e-9) return null;
|
|
4374
|
+
keep = [[0, lo], [hi, len]].filter(([x, y]) => y - x > 1e-9);
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
const out = contours.slice(0, index);
|
|
4378
|
+
for (const [u0, u1] of keep) {
|
|
4379
|
+
const run = span(ring, u0, u1);
|
|
4380
|
+
if (run.length) out.push({ segments: snapEnds2(run, [a, b], tol), closed: false });
|
|
4381
|
+
}
|
|
4382
|
+
out.push(...contours.slice(index + 1));
|
|
4383
|
+
return contoursToPath(out);
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
// src/canvas/pathops/hatch.ts
|
|
4387
|
+
function hatchPath(path, options = {}) {
|
|
4388
|
+
const out = [];
|
|
4389
|
+
const b = bezierPathBounds(path);
|
|
4390
|
+
if (!b || b.maxX - b.minX <= 0 || b.maxY - b.minY <= 0) return out;
|
|
4391
|
+
const want = Math.max(_nullishCoalesce(options.gap, () => ( 4)), 1e-3);
|
|
4392
|
+
const s = Math.SQRT1_2;
|
|
4393
|
+
const rot = (p) => [(p[0] + p[1]) * s, (p[1] - p[0]) * s];
|
|
4394
|
+
const unrot = (x, y) => ({ x: (x - y) * s, y: (x + y) * s });
|
|
4395
|
+
const edges = [];
|
|
4396
|
+
for (const contour of pathToContours(path)) {
|
|
4397
|
+
const pts = flattenChain(closeContour(contour, true), want * 0.1).map(rot);
|
|
4398
|
+
for (let i = 0; i < pts.length; i++) {
|
|
4399
|
+
const p = pts[i];
|
|
4400
|
+
const q = pts[(i + 1) % pts.length];
|
|
4401
|
+
if (p[0] !== q[0] || p[1] !== q[1]) edges.push([p, q]);
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
let y0 = Infinity;
|
|
4405
|
+
let y1 = -Infinity;
|
|
4406
|
+
for (const [p, q] of edges) {
|
|
4407
|
+
y0 = Math.min(y0, p[1], q[1]);
|
|
4408
|
+
y1 = Math.max(y1, p[1], q[1]);
|
|
4409
|
+
}
|
|
4410
|
+
if (!(y1 > y0)) return out;
|
|
4411
|
+
const gap = Math.max(want, (y1 - y0) / Math.max(1, _nullishCoalesce(options.maxLines, () => ( 400))), 1e-6);
|
|
4412
|
+
const xs = [];
|
|
4413
|
+
for (let y = Math.floor(y0 / gap) * gap + gap / 2; y < y1; y += gap) {
|
|
4414
|
+
xs.length = 0;
|
|
4415
|
+
for (const [p, q] of edges) if (p[1] <= y !== q[1] <= y) xs.push(p[0] + (y - p[1]) / (q[1] - p[1]) * (q[0] - p[0]));
|
|
4416
|
+
xs.sort((m, n) => m - n);
|
|
4417
|
+
for (let i = 0; i + 1 < xs.length; i += 2) {
|
|
4418
|
+
out.push({ type: "M", ...unrot(xs[i], y) }, { type: "L", ...unrot(xs[i + 1], y) });
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
return out;
|
|
4422
|
+
}
|
|
4423
|
+
|
|
4424
|
+
// src/canvas/pathops/pathfinder.ts
|
|
4425
|
+
var PATHFINDER_OPERATIONS = ["divide", "trim", "merge", "crop", "outline", "minusBack"];
|
|
4426
|
+
var top = (covering) => covering[covering.length - 1];
|
|
4427
|
+
function pathfinder(op, shapes, options = {}) {
|
|
4428
|
+
const n = shapes.length;
|
|
4429
|
+
if (n === 0) return { pieces: [], engine: "curve" };
|
|
4430
|
+
const filled = shapes.filter((s) => !s.line);
|
|
4431
|
+
const operands = shapes.map((s) => ({ path: s.path, fillRule: _nullishCoalesce(s.fillRule, () => ( "nonzero")) }));
|
|
4432
|
+
if (op === "outline") {
|
|
4433
|
+
const map = PlanarMap.build(
|
|
4434
|
+
shapes.map((s) => ({ path: s.line ? s.path : closeAll(s.path), fills: false })),
|
|
4435
|
+
options
|
|
4436
|
+
);
|
|
4437
|
+
if (!map) return { pieces: [], engine: "curve", error: "could not map the outlines" };
|
|
4438
|
+
const pieces = map.chains().map((c) => ({ path: contoursToPath([{ segments: c.segments, closed: c.closed }]), source: c.source, open: !c.closed }));
|
|
4439
|
+
return { pieces, engine: "curve" };
|
|
4440
|
+
}
|
|
4441
|
+
if (op === "divide") {
|
|
4442
|
+
if (filled.length !== n) {
|
|
4443
|
+
const map = PlanarMap.build(shapes.map((s) => ({ path: s.line ? s.path : closeAll(s.path), fillRule: s.fillRule, fills: !s.line })), options);
|
|
4444
|
+
if (!map) return { pieces: [], engine: "curve", error: "could not map the shapes" };
|
|
4445
|
+
const pieces = [];
|
|
4446
|
+
map.faces.forEach((face, i) => {
|
|
4447
|
+
const covering = map.faceSources(i);
|
|
4448
|
+
if (covering.length) pieces.push({ path: face.path, source: top(covering) });
|
|
4449
|
+
});
|
|
4450
|
+
return { pieces, engine: "curve" };
|
|
4451
|
+
}
|
|
4452
|
+
const result = dividePaths(operands, options);
|
|
4453
|
+
return { ...report(result), pieces: result.pieces.map((p) => ({ path: p.path, source: top(p.operands) })) };
|
|
4454
|
+
}
|
|
4455
|
+
const index = shapes.flatMap((s, i) => s.line ? [] : [i]);
|
|
4456
|
+
const m = index.length;
|
|
4457
|
+
if (m === 0) return { pieces: [], engine: "curve" };
|
|
4458
|
+
const ops = index.map((i) => operands[i]);
|
|
4459
|
+
const key = (k) => _nullishCoalesce(shapes[index[k]].key, () => ( index[k]));
|
|
4460
|
+
const select = (selectors) => pathBooleanSelect(ops, selectors, options);
|
|
4461
|
+
const joined = (parts) => parts.flat();
|
|
4462
|
+
switch (op) {
|
|
4463
|
+
case "trim": {
|
|
4464
|
+
const result = select(index.map((_, k) => (c) => c.length > 0 && top(c) === k));
|
|
4465
|
+
const pieces = result.parts.flatMap((parts, k) => parts.length ? [{ path: joined(parts), source: index[k] }] : []);
|
|
4466
|
+
return { ...report(result), pieces };
|
|
4467
|
+
}
|
|
4468
|
+
case "merge": {
|
|
4469
|
+
const keys = [];
|
|
4470
|
+
for (let k = 0; k < m; k++) if (!keys.includes(key(k))) keys.push(key(k));
|
|
4471
|
+
const result = select(keys.map((kk) => (c) => c.length > 0 && key(top(c)) === kk));
|
|
4472
|
+
const pieces = [];
|
|
4473
|
+
result.parts.forEach((parts, j) => {
|
|
4474
|
+
let source = 0;
|
|
4475
|
+
for (let k = 0; k < m; k++) if (key(k) === keys[j]) source = index[k];
|
|
4476
|
+
for (const part of parts) pieces.push({ path: part, source });
|
|
4477
|
+
});
|
|
4478
|
+
return { ...report(result), pieces };
|
|
4479
|
+
}
|
|
4480
|
+
case "crop": {
|
|
4481
|
+
if (m < 2) return { pieces: [], engine: "curve" };
|
|
4482
|
+
const front = m - 1;
|
|
4483
|
+
const result = select(
|
|
4484
|
+
index.slice(0, front).map((_, k) => (c) => c.length >= 2 && top(c) === front && c[c.length - 2] === k)
|
|
4485
|
+
);
|
|
4486
|
+
const pieces = result.parts.flatMap((parts, k) => parts.length ? [{ path: joined(parts), source: index[k] }] : []);
|
|
4487
|
+
return { ...report(result), pieces };
|
|
4488
|
+
}
|
|
4489
|
+
case "minusBack": {
|
|
4490
|
+
const front = m - 1;
|
|
4491
|
+
const result = select([(c) => c.length === 1 && c[0] === front]);
|
|
4492
|
+
const parts = result.parts[0];
|
|
4493
|
+
return { ...report(result), pieces: parts.length ? [{ path: joined(parts), source: index[front] }] : [] };
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
return { pieces: [], engine: "curve" };
|
|
4497
|
+
}
|
|
4498
|
+
function report(r) {
|
|
4499
|
+
return { engine: r.engine, ...r.fallbackReason ? { fallbackReason: r.fallbackReason } : {}, ...r.error ? { error: r.error } : {} };
|
|
4500
|
+
}
|
|
4501
|
+
function closeAll(path) {
|
|
4502
|
+
return contoursToPath(pathToContours(path).map((c) => ({ segments: c.segments, closed: true })));
|
|
4503
|
+
}
|
|
4504
|
+
|
|
4505
|
+
|
|
4506
|
+
|
|
4507
|
+
|
|
4508
|
+
|
|
4509
|
+
|
|
4510
|
+
|
|
4511
|
+
|
|
4512
|
+
|
|
4513
|
+
|
|
3734
4514
|
|
|
3735
4515
|
|
|
3736
4516
|
|
|
@@ -3811,5 +4591,5 @@ function offsetPath(input, distance, options = {}) {
|
|
|
3811
4591
|
|
|
3812
4592
|
|
|
3813
4593
|
|
|
3814
|
-
exports.ringSignedArea = ringSignedArea; exports.polygonArea = polygonArea; exports.multiPolygonArea = multiPolygonArea; exports.multiPolygonBounds = multiPolygonBounds; exports.pointInRings = pointInRings; exports.pointInMultiPolygon = pointInMultiPolygon; exports.polygonBoolean = polygonBoolean; exports.unionPolygons = unionPolygons; exports.intersectPolygons = intersectPolygons; exports.subtractPolygons = subtractPolygons; exports.xorPolygons = xorPolygons; exports.dividePolygons = dividePolygons; exports.cubicPointAt = cubicPointAt; exports.fitCubicWithTangents = fitCubicWithTangents; exports.fitCubicRun = fitCubicRun; exports.fitCubics = fitCubics; exports.isStraightCubic = isStraightCubic; exports.cubicsToPathData = cubicsToPathData; exports.fitPathData = fitPathData; exports.fitFreehandPath = fitFreehandPath; exports.CANVAS_GRADIENT_PRESETS = CANVAS_GRADIENT_PRESETS; exports.isCanvasGradient = isCanvasGradient; exports.parseCssGradient = parseCssGradient; exports.resolveCanvasGradient = resolveCanvasGradient; exports.angleToSvgLinearCoords = angleToSvgLinearCoords; exports.gradientToSvgDef = gradientToSvgDef; exports.SHAPE_KINDS = SHAPE_KINDS; exports.isShapeDescription = isShapeDescription; exports.parseSvgPath = parseSvgPath; exports.flattenSvgPath = flattenSvgPath; exports.getShapeSvg = getShapeSvg; exports.getShapeRenderData = getShapeRenderData; exports.shapeToPolygon = shapeToPolygon; exports.formatNumber = formatNumber2; exports.simplifyRing = simplifyRing; exports.polygonToSvgPath = polygonToSvgPath; exports.segmentPoint = segmentPoint; exports.segmentDerivative = segmentDerivative; exports.segmentTangent = segmentTangent; exports.segmentCurvature = segmentCurvature; exports.splitSegment = splitSegment; exports.subSegment = subSegment; exports.segmentExtrema = segmentExtrema; exports.segmentBounds = segmentBounds; exports.segmentLength = segmentLength; exports.segmentParamAtLength = segmentParamAtLength; exports.segmentNearest = segmentNearest; exports.arcToCubics = arcToCubics; exports.toBezierPath = toBezierPath; exports.pathToContours = pathToContours; exports.contoursToPath = contoursToPath; exports.bezierPathToSvg = bezierPathToSvg; exports.transformBezierPath = transformBezierPath; exports.countPathSegments = countPathSegments; exports.closeContour = closeContour; exports.bezierPathBounds = bezierPathBounds; exports.bezierPathSignedArea = bezierPathSignedArea; exports.contourSignedArea = contourSignedArea; exports.bezierPathLength = bezierPathLength; exports.bezierPathPointAtLength = bezierPathPointAtLength; exports.trimSegments = trimSegments; exports.windingNumber = windingNumber; exports.pointInBezierPath = pointInBezierPath; exports.distanceToBezierPath = distanceToBezierPath; exports.flattenChain = flattenChain; exports.PathOpsError = PathOpsError; exports.segmentIntersections = segmentIntersections; exports.pathBoolean = pathBoolean; exports.unitePaths = unitePaths; exports.intersectPaths = intersectPaths; exports.subtractPaths = subtractPaths; exports.excludePaths = excludePaths; exports.dividePaths = dividePaths; exports.normalizeBezierPath = normalizeBezierPath; exports.bezierPathArea = bezierPathArea; exports.dashContours = dashContours; exports.strokeOutlineRings = strokeOutlineRings; exports.outlineStroke = outlineStroke; exports.offsetPath = offsetPath;
|
|
3815
|
-
//# sourceMappingURL=chunk-
|
|
4594
|
+
exports.ringSignedArea = ringSignedArea; exports.polygonArea = polygonArea; exports.multiPolygonArea = multiPolygonArea; exports.multiPolygonBounds = multiPolygonBounds; exports.pointInRings = pointInRings; exports.pointInMultiPolygon = pointInMultiPolygon; exports.polygonBoolean = polygonBoolean; exports.unionPolygons = unionPolygons; exports.intersectPolygons = intersectPolygons; exports.subtractPolygons = subtractPolygons; exports.xorPolygons = xorPolygons; exports.dividePolygons = dividePolygons; exports.cubicPointAt = cubicPointAt; exports.fitCubicWithTangents = fitCubicWithTangents; exports.fitCubicRun = fitCubicRun; exports.fitCubics = fitCubics; exports.isStraightCubic = isStraightCubic; exports.cubicsToPathData = cubicsToPathData; exports.fitPathData = fitPathData; exports.fitFreehandPath = fitFreehandPath; exports.CANVAS_GRADIENT_PRESETS = CANVAS_GRADIENT_PRESETS; exports.isCanvasGradient = isCanvasGradient; exports.parseCssGradient = parseCssGradient; exports.resolveCanvasGradient = resolveCanvasGradient; exports.angleToSvgLinearCoords = angleToSvgLinearCoords; exports.gradientToSvgDef = gradientToSvgDef; exports.SHAPE_KINDS = SHAPE_KINDS; exports.isShapeDescription = isShapeDescription; exports.parseSvgPath = parseSvgPath; exports.flattenSvgPath = flattenSvgPath; exports.getShapeSvg = getShapeSvg; exports.getShapeRenderData = getShapeRenderData; exports.shapeToPolygon = shapeToPolygon; exports.formatNumber = formatNumber2; exports.simplifyRing = simplifyRing; exports.polygonToSvgPath = polygonToSvgPath; exports.segmentPoint = segmentPoint; exports.segmentDerivative = segmentDerivative; exports.segmentTangent = segmentTangent; exports.segmentCurvature = segmentCurvature; exports.splitSegment = splitSegment; exports.subSegment = subSegment; exports.segmentExtrema = segmentExtrema; exports.segmentBounds = segmentBounds; exports.segmentLength = segmentLength; exports.segmentParamAtLength = segmentParamAtLength; exports.segmentNearest = segmentNearest; exports.arcToCubics = arcToCubics; exports.toBezierPath = toBezierPath; exports.pathToContours = pathToContours; exports.contoursToPath = contoursToPath; exports.bezierPathToSvg = bezierPathToSvg; exports.transformBezierPath = transformBezierPath; exports.countPathSegments = countPathSegments; exports.closeContour = closeContour; exports.bezierPathBounds = bezierPathBounds; exports.bezierPathSignedArea = bezierPathSignedArea; exports.contourSignedArea = contourSignedArea; exports.bezierPathLength = bezierPathLength; exports.bezierPathPointAtLength = bezierPathPointAtLength; exports.trimSegments = trimSegments; exports.windingNumber = windingNumber; exports.pointInBezierPath = pointInBezierPath; exports.distanceToBezierPath = distanceToBezierPath; exports.flattenChain = flattenChain; exports.PathOpsError = PathOpsError; exports.segmentIntersections = segmentIntersections; exports.pathBoolean = pathBoolean; exports.unitePaths = unitePaths; exports.intersectPaths = intersectPaths; exports.subtractPaths = subtractPaths; exports.excludePaths = excludePaths; exports.dividePaths = dividePaths; exports.normalizeBezierPath = normalizeBezierPath; exports.bezierPathArea = bezierPathArea; exports.planarSplit = planarSplit; exports.pathBooleanSelect = pathBooleanSelect; exports.dashContours = dashContours; exports.strokeOutlineRings = strokeOutlineRings; exports.outlineStroke = outlineStroke; exports.offsetPath = offsetPath; exports.interiorPoint = interiorPoint; exports.PlanarMap = PlanarMap; exports.contourEnclosesArea = contourEnclosesArea; exports.cutOutPiece = cutOutPiece; exports.hatchPath = hatchPath; exports.PATHFINDER_OPERATIONS = PATHFINDER_OPERATIONS; exports.pathfinder = pathfinder;
|
|
4595
|
+
//# sourceMappingURL=chunk-VNYRI265.cjs.map
|