@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-VNYRI265.cjs","../src/canvas/polygonBoolean.ts","../src/canvas/curveFit.ts","../src/canvas/gradient.ts","../src/canvas/shapeGeometry.ts","../src/canvas/pathops/bezier.ts","../src/canvas/pathops/boolean.ts","../src/canvas/pathops/stroke.ts","../src/canvas/pathops/planar.ts","../src/canvas/pathops/hatch.ts","../src/canvas/pathops/pathfinder.ts"],"names":["dist","TWO_PI","directed","span","corners","out","dot","add","right","len","closed"],"mappings":"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A;AACD,MAAA;AAC1B,IAAA;AACD,EAAA;AACO,EAAA;AACR;AT6wIoC;AACA;AUnzIkC;AA6BhB;AAekC;AACtE,EAAA;AACiB,EAAA;AACJ,EAAA;AACD,EAAA;AAEP,EAAA;AACC,IAAA;AACQ,MAAA;AAC7B,MAAA;AACD,IAAA;AAC+B,IAAA;AACC,IAAA;AACP,IAAA;AAC1B,EAAA;AAEqB,EAAA;AACK,IAAA;AACI,MAAA;AACA,MAAA;AACO,MAAA;AACV,MAAA;AACH,QAAA;AACO,QAAA;AAC5B,MAAA;AACwB,MAAA;AAC1B,IAAA;AAC2B,IAAA;AACC,IAAA;AAC7B,EAAA;AAGiC,EAAA;AACjB,EAAA;AACkB,EAAA;AACL,EAAA;AACa,EAAA;AACkC,EAAA;AAC9B,EAAA;AAElC,EAAA;AACE,IAAA;AACgB,MAAA;AACA,MAAA;AACA,MAAA;AAC7B,IAAA;AACc,IAAA;AACsB,MAAA;AACP,MAAA;AACD,MAAA;AACQ,MAAA;AACN,MAAA;AAEf,QAAA;AACe,QAAA;AACF,QAAA;AAC1B,MAAA;AAC2B,MAAA;AAC7B,IAAA;AACa,IAAA;AACiB,MAAA;AACX,MAAA;AACH,MAAA;AACa,QAAA;AAC5B,MAAA;AAC4B,MAAA;AACA,MAAA;AAC7B,IAAA;AACkB,IAAA;AACC,MAAA;AACY,MAAA;AACF,MAAA;AACA,MAAA;AAC7B,IAAA;AACD,EAAA;AAC6B,EAAA;AAC9B;AAEiD;AACf,EAAA;AAClC;AAGqD;AAC9B,EAAA;AACvB;AVkwIoC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-VNYRI265.cjs","sourcesContent":[null,"/**\n * Polygon boolean operations (union, intersection, difference, xor and divide) on\n * multi-polygons with holes.\n *\n * Algorithm (overlay + winding classification):\n * 1. Every input edge is collected into one segment set. Vertices are merged through a\n * tolerance-aware point pool, so points closer than `epsilon` become one vertex.\n * 2. Segments are split at every crossing, T-junction and collinear overlap until no two\n * segments cross (a planar arrangement). Splitting is repeated because snapping a new\n * vertex can move a segment by up to `epsilon`.\n * 3. Coincident segments (shared edges) are merged, keeping for every operand the signed\n * number of its edges that run along the segment.\n * 4. For every segment the winding number of each operand on its right side is computed\n * with a ray cast from the segment midpoint (the segment itself excluded), and the left\n * side follows from the signed multiplicities. The fill rule turns windings into\n * inside / outside flags and the operation decides whether each side is in the result.\n * 5. Segments with the result on exactly one side are kept, oriented with the result on\n * their left, and traced into rings (taking the sharpest left turn at shared vertices,\n * so rings that touch at a vertex stay separate simple rings). Rings with a positive\n * signed area are outer rings; negative ones are holes, assigned to the smallest outer\n * ring that contains them.\n *\n * Handles shared edges, touching vertices, collinear overlaps, holes, nested and disjoint\n * inputs and self-intersecting input rings (resolved by the operand's fill rule).\n *\n * Limits: the overlay is O(n²) in the number of edges (pruned by a sweep on x), which is\n * comfortable for canvas shapes (thousands of edges) but not for GIS-scale data. Features\n * narrower than `epsilon` collapse. Coordinates must be finite.\n */\nimport type { Point } from './types.js';\n\n/** A closed ring of points. The closing point may be repeated or omitted. */\nexport type PolygonRing = Point[];\n/** A polygon: the outer ring followed by its holes. */\nexport type PolygonWithHoles = PolygonRing[];\n/** A set of polygons. */\nexport type MultiPolygon = PolygonWithHoles[];\n\n/** SVG fill rule used to decide which areas of a ring set are inside. */\nexport type PolygonFillRule = 'nonzero' | 'evenodd';\n\n/** Boolean operations. `xor` is Freeform's Exclude. */\nexport type PolygonBooleanOperation = 'union' | 'intersection' | 'difference' | 'xor';\n\n/**\n * A raw ring set with a fill rule (e.g. flattened SVG path data). Ring orientation is\n * significant for `nonzero`.\n */\nexport interface PolygonOperand {\n\trings: PolygonRing[];\n\tfillRule?: PolygonFillRule;\n}\n\nexport type PolygonInput = MultiPolygon | PolygonOperand;\n\nexport interface PolygonBooleanOptions {\n\t/**\n\t * Distance below which points are treated as equal. Defaults to `1e-9` times the\n\t * largest coordinate magnitude of the inputs (at least `1e-9`).\n\t */\n\tepsilon?: number;\n}\n\n/** One region of `dividePolygons`, with the indices of the operands that cover it. */\nexport interface PolygonPiece {\n\tpolygon: PolygonWithHoles;\n\toperands: number[];\n}\n\n// ---------------------------------------------------------------------------\n// Basic measures\n// ---------------------------------------------------------------------------\n\n/** Signed shoelace area of a ring (positive when counter-clockwise in a y-up frame). */\nexport function ringSignedArea(ring: readonly Point[]): number {\n\tlet sum = 0;\n\tconst n = ring.length;\n\tfor (let i = 0; i < n; i++) {\n\t\tconst [x1, y1] = ring[i];\n\t\tconst [x2, y2] = ring[(i + 1) % n];\n\t\tsum += x1 * y2 - x2 * y1;\n\t}\n\treturn sum / 2;\n}\n\n/** Area of a polygon (outer ring minus holes). */\nexport function polygonArea(polygon: PolygonWithHoles): number {\n\tif (polygon.length === 0) return 0;\n\tlet area = Math.abs(ringSignedArea(polygon[0]));\n\tfor (let i = 1; i < polygon.length; i++) area -= Math.abs(ringSignedArea(polygon[i]));\n\treturn area;\n}\n\n/** Total area of a multi-polygon (holes subtracted). */\nexport function multiPolygonArea(multi: MultiPolygon): number {\n\tlet total = 0;\n\tfor (const polygon of multi) total += polygonArea(polygon);\n\treturn total;\n}\n\n/** Axis-aligned bounds of a multi-polygon (or ring list), or `null` when empty. */\nexport function multiPolygonBounds(\n\tinput: MultiPolygon | PolygonRing[]\n): { minX: number; minY: number; maxX: number; maxY: number } | null {\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tconst visit = (ring: PolygonRing) => {\n\t\tfor (const [x, y] of ring) {\n\t\t\tif (x < minX) minX = x;\n\t\t\tif (y < minY) minY = y;\n\t\t\tif (x > maxX) maxX = x;\n\t\t\tif (y > maxY) maxY = y;\n\t\t}\n\t};\n\tfor (const item of input as any[]) {\n\t\tif (item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0])) {\n\t\t\tfor (const ring of item as PolygonRing[]) visit(ring);\n\t\t} else {\n\t\t\tvisit(item as PolygonRing);\n\t\t}\n\t}\n\treturn minX === Infinity ? null : { minX, minY, maxX, maxY };\n}\n\n/**\n * Whether a point is inside a ring set under a fill rule. Points exactly on an edge may\n * report either side.\n */\nexport function pointInRings(\n\tpoint: Point,\n\trings: readonly (readonly Point[])[],\n\tfillRule: PolygonFillRule = 'nonzero'\n): boolean {\n\tconst [px, py] = point;\n\tlet winding = 0;\n\tfor (const ring of rings) {\n\t\tconst n = ring.length;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst [ax, ay] = ring[i];\n\t\t\tconst [bx, by] = ring[(i + 1) % n];\n\t\t\tif (ay <= py && by > py) {\n\t\t\t\tif (ax + ((py - ay) / (by - ay)) * (bx - ax) > px) winding++;\n\t\t\t} else if (by <= py && ay > py) {\n\t\t\t\tif (ax + ((py - ay) / (by - ay)) * (bx - ax) > px) winding--;\n\t\t\t}\n\t\t}\n\t}\n\treturn fillRule === 'evenodd' ? (winding & 1) === 1 : winding !== 0;\n}\n\n/** Whether a point is inside a multi-polygon (holes excluded). */\nexport function pointInMultiPolygon(point: Point, multi: MultiPolygon): boolean {\n\treturn multi.some((polygon) => pointInRings(point, polygon, 'evenodd'));\n}\n\n// ---------------------------------------------------------------------------\n// Operand normalisation\n// ---------------------------------------------------------------------------\n\nfunction cleanRing(ring: readonly Point[]): Point[] {\n\tconst out: Point[] = [];\n\tfor (const p of ring) {\n\t\tif (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;\n\t\tconst last = out[out.length - 1];\n\t\tif (last && last[0] === p[0] && last[1] === p[1]) continue;\n\t\tout.push([p[0], p[1]]);\n\t}\n\twhile (out.length > 1) {\n\t\tconst first = out[0];\n\t\tconst last = out[out.length - 1];\n\t\tif (first[0] === last[0] && first[1] === last[1]) out.pop();\n\t\telse break;\n\t}\n\treturn out;\n}\n\nfunction isOperand(input: PolygonInput): input is PolygonOperand {\n\treturn !Array.isArray(input) && typeof input === 'object' && input !== null && Array.isArray(input.rings);\n}\n\ninterface NormalizedOperand {\n\trings: Point[][];\n\tfillRule: PolygonFillRule;\n}\n\n/**\n * Multi-polygons are re-oriented (outer rings positive, holes negative) and filled with\n * `nonzero`, so overlapping polygons in one operand behave as their union.\n */\nfunction normalizeInput(input: PolygonInput): NormalizedOperand {\n\tif (isOperand(input)) {\n\t\treturn {\n\t\t\trings: input.rings.map(cleanRing).filter((r) => r.length >= 3),\n\t\t\tfillRule: input.fillRule ?? 'nonzero'\n\t\t};\n\t}\n\tconst rings: Point[][] = [];\n\tfor (const polygon of input ?? []) {\n\t\tpolygon.forEach((raw, index) => {\n\t\t\tconst ring = cleanRing(raw);\n\t\t\tif (ring.length < 3) return;\n\t\t\tconst area = ringSignedArea(ring);\n\t\t\tif (area === 0) return;\n\t\t\tconst wantPositive = index === 0;\n\t\t\trings.push(area > 0 === wantPositive ? ring : ring.reverse());\n\t\t});\n\t}\n\treturn { rings, fillRule: 'nonzero' };\n}\n\n// ---------------------------------------------------------------------------\n// Overlay\n// ---------------------------------------------------------------------------\n\ninterface Vertex {\n\tid: number;\n\tx: number;\n\ty: number;\n}\n\n/** Merges points closer than `eps` (grid hash with neighbour lookup). */\nclass PointPool {\n\treadonly vertices: Vertex[] = [];\n\tprivate readonly cells = new Map<string, number[]>();\n\tconstructor(private readonly eps: number) {}\n\n\tget(x: number, y: number): Vertex {\n\t\tconst cx = Math.floor(x / this.eps);\n\t\tconst cy = Math.floor(y / this.eps);\n\t\tlet best: Vertex | null = null;\n\t\tlet bestDist = Infinity;\n\t\tfor (let dx = -1; dx <= 1; dx++) {\n\t\t\tfor (let dy = -1; dy <= 1; dy++) {\n\t\t\t\tconst ids = this.cells.get(`${cx + dx},${cy + dy}`);\n\t\t\t\tif (!ids) continue;\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tconst v = this.vertices[id];\n\t\t\t\t\tconst d = Math.hypot(v.x - x, v.y - y);\n\t\t\t\t\tif (d <= this.eps && d < bestDist) {\n\t\t\t\t\t\tbest = v;\n\t\t\t\t\t\tbestDist = d;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (best) return best;\n\t\tconst vertex: Vertex = { id: this.vertices.length, x, y };\n\t\tthis.vertices.push(vertex);\n\t\tconst key = `${cx},${cy}`;\n\t\tconst bucket = this.cells.get(key);\n\t\tif (bucket) bucket.push(vertex.id);\n\t\telse this.cells.set(key, [vertex.id]);\n\t\treturn vertex;\n\t}\n}\n\ninterface RawSegment {\n\ta: Vertex;\n\tb: Vertex;\n\toperand: number;\n}\n\ninterface Splits {\n\tsegment: RawSegment;\n\tpoints: Map<number, { t: number; v: Vertex }>;\n}\n\n/** Projection parameter of `p` on `a→b` and its distance to the segment line. */\nfunction project(a: Vertex, b: Vertex, p: Vertex): { t: number; dist: number } {\n\tconst dx = b.x - a.x;\n\tconst dy = b.y - a.y;\n\tconst len2 = dx * dx + dy * dy;\n\tconst t = len2 === 0 ? 0 : ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;\n\tconst cx = a.x + t * dx;\n\tconst cy = a.y + t * dy;\n\treturn { t, dist: Math.hypot(p.x - cx, p.y - cy) };\n}\n\nfunction addSplit(splits: Splits, t: number, v: Vertex) {\n\tconst { a, b } = splits.segment;\n\tif (v.id === a.id || v.id === b.id) return;\n\tif (!splits.points.has(v.id)) splits.points.set(v.id, { t, v });\n}\n\n/** One pass: splits every segment at crossings / touches with other segments. */\nfunction splitPass(segments: RawSegment[], pool: PointPool, eps: number): { segments: RawSegment[]; changed: boolean } {\n\tconst order = segments\n\t\t.map((s, i) => ({\n\t\t\ti,\n\t\t\tminX: Math.min(s.a.x, s.b.x),\n\t\t\tmaxX: Math.max(s.a.x, s.b.x),\n\t\t\tminY: Math.min(s.a.y, s.b.y),\n\t\t\tmaxY: Math.max(s.a.y, s.b.y)\n\t\t}))\n\t\t.sort((p, q) => p.minX - q.minX);\n\tconst splits: Splits[] = segments.map((segment) => ({ segment, points: new Map() }));\n\n\tfor (let oi = 0; oi < order.length; oi++) {\n\t\tconst bi = order[oi];\n\t\tconst s1 = segments[bi.i];\n\t\tfor (let oj = oi + 1; oj < order.length; oj++) {\n\t\t\tconst bj = order[oj];\n\t\t\tif (bj.minX > bi.maxX + eps) break;\n\t\t\tif (bj.minY > bi.maxY + eps || bj.maxY < bi.minY - eps) continue;\n\t\t\tconst s2 = segments[bj.i];\n\t\t\tconst sharedA = s1.a.id === s2.a.id || s1.a.id === s2.b.id;\n\t\t\tconst sharedB = s1.b.id === s2.a.id || s1.b.id === s2.b.id;\n\t\t\tif (sharedA && sharedB) continue; // identical (or reversed) segment\n\n\t\t\tlet touched = false;\n\t\t\t// Endpoints of one segment lying on the other: T-junctions and collinear overlaps.\n\t\t\tfor (const [seg, other, split] of [\n\t\t\t\t[s1, s2, splits[bi.i]],\n\t\t\t\t[s2, s1, splits[bj.i]]\n\t\t\t] as const) {\n\t\t\t\tfor (const p of [other.a, other.b]) {\n\t\t\t\t\tif (p.id === seg.a.id || p.id === seg.b.id) continue;\n\t\t\t\t\tconst { t, dist } = project(seg.a, seg.b, p);\n\t\t\t\t\tconst len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);\n\t\t\t\t\tif (dist <= eps && t * len > eps && (1 - t) * len > eps) {\n\t\t\t\t\t\taddSplit(split, t, p);\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (touched) continue;\n\n\t\t\t// Proper crossing.\n\t\t\tconst d1x = s1.b.x - s1.a.x;\n\t\t\tconst d1y = s1.b.y - s1.a.y;\n\t\t\tconst d2x = s2.b.x - s2.a.x;\n\t\t\tconst d2y = s2.b.y - s2.a.y;\n\t\t\tconst denom = d1x * d2y - d1y * d2x;\n\t\t\tconst len1 = Math.hypot(d1x, d1y);\n\t\t\tconst len2 = Math.hypot(d2x, d2y);\n\t\t\tif (Math.abs(denom) <= 1e-12 * len1 * len2) continue; // parallel\n\t\t\tconst ex = s2.a.x - s1.a.x;\n\t\t\tconst ey = s2.a.y - s1.a.y;\n\t\t\tconst t = (ex * d2y - ey * d2x) / denom;\n\t\t\tconst u = (ex * d1y - ey * d1x) / denom;\n\t\t\tconst tolT = eps / len1;\n\t\t\tconst tolU = eps / len2;\n\t\t\tif (t < -tolT || t > 1 + tolT || u < -tolU || u > 1 + tolU) continue;\n\t\t\tif (sharedA || sharedB) continue; // they meet at the shared endpoint only\n\t\t\tconst v = pool.get(s1.a.x + t * d1x, s1.a.y + t * d1y);\n\t\t\taddSplit(splits[bi.i], project(s1.a, s1.b, v).t, v);\n\t\t\taddSplit(splits[bj.i], project(s2.a, s2.b, v).t, v);\n\t\t}\n\t}\n\n\tlet changed = false;\n\tconst out: RawSegment[] = [];\n\tfor (const { segment, points } of splits) {\n\t\tif (points.size === 0) {\n\t\t\tout.push(segment);\n\t\t\tcontinue;\n\t\t}\n\t\tchanged = true;\n\t\tconst ordered = [...points.values()].sort((p, q) => p.t - q.t);\n\t\tlet prev = segment.a;\n\t\tfor (const { v } of ordered) {\n\t\t\tif (v.id !== prev.id) out.push({ a: prev, b: v, operand: segment.operand });\n\t\t\tprev = v;\n\t\t}\n\t\tif (prev.id !== segment.b.id) out.push({ a: prev, b: segment.b, operand: segment.operand });\n\t}\n\treturn { segments: out, changed };\n}\n\ninterface Edge {\n\ta: Vertex;\n\tb: Vertex;\n\t/** Signed multiplicity per operand (+1 per edge running a→b, −1 per edge running b→a). */\n\tmult: number[];\n}\n\ninterface Overlay {\n\tedges: Edge[];\n\t/** Winding number per operand on the right of each edge. */\n\tright: number[][];\n\t/** Winding number per operand on the left of each edge. */\n\tleft: number[][];\n\tfillRules: PolygonFillRule[];\n\teps: number;\n}\n\nfunction defaultEpsilon(operands: NormalizedOperand[]): number {\n\tlet max = 0;\n\tfor (const op of operands) {\n\t\tfor (const ring of op.rings) {\n\t\t\tfor (const [x, y] of ring) {\n\t\t\t\tconst m = Math.max(Math.abs(x), Math.abs(y));\n\t\t\t\tif (m > max) max = m;\n\t\t\t}\n\t\t}\n\t}\n\treturn Math.max(1e-9, max * 1e-9);\n}\n\nfunction buildOverlay(inputs: PolygonInput[], options: PolygonBooleanOptions): Overlay {\n\tconst operands = inputs.map(normalizeInput);\n\tconst eps = options.epsilon && options.epsilon > 0 ? options.epsilon : defaultEpsilon(operands);\n\tconst pool = new PointPool(eps);\n\tlet segments: RawSegment[] = [];\n\toperands.forEach((op, operand) => {\n\t\tfor (const ring of op.rings) {\n\t\t\tconst verts = ring.map(([x, y]) => pool.get(x, y));\n\t\t\tfor (let i = 0; i < verts.length; i++) {\n\t\t\t\tconst a = verts[i];\n\t\t\t\tconst b = verts[(i + 1) % verts.length];\n\t\t\t\tif (a.id !== b.id) segments.push({ a, b, operand });\n\t\t\t}\n\t\t}\n\t});\n\n\tfor (let pass = 0; pass < 8; pass++) {\n\t\tconst result = splitPass(segments, pool, eps);\n\t\tsegments = result.segments;\n\t\tif (!result.changed) break;\n\t}\n\n\t// Merge coincident segments.\n\tconst count = operands.length;\n\tconst byKey = new Map<string, Edge>();\n\tfor (const s of segments) {\n\t\tconst forward = s.a.id < s.b.id;\n\t\tconst a = forward ? s.a : s.b;\n\t\tconst b = forward ? s.b : s.a;\n\t\tconst key = `${a.id},${b.id}`;\n\t\tlet edge = byKey.get(key);\n\t\tif (!edge) {\n\t\t\tedge = { a, b, mult: new Array(count).fill(0) };\n\t\t\tbyKey.set(key, edge);\n\t\t}\n\t\tedge.mult[s.operand] += forward ? 1 : -1;\n\t}\n\tconst edges = [...byKey.values()].filter((e) => e.mult.some((m) => m !== 0));\n\n\tconst right: number[][] = [];\n\tconst left: number[][] = [];\n\t// Rays only meet edges whose span on the ray's axis covers the ray: index the edges in bands\n\t// along each of the two axes rays run across, so each ray tests one band, not every edge.\n\tconst bandCount = Math.max(1, Math.min(4096, Math.ceil(Math.sqrt(edges.length))));\n\tconst bandIndex = (horizontal: boolean) => {\n\t\tconst lo = (e: Overlay['edges'][number]) => (horizontal ? Math.min(-e.a.x, -e.b.x) : Math.min(e.a.y, e.b.y));\n\t\tconst hi = (e: Overlay['edges'][number]) => (horizontal ? Math.max(-e.a.x, -e.b.x) : Math.max(e.a.y, e.b.y));\n\t\tlet min = Infinity;\n\t\tlet max = -Infinity;\n\t\tfor (const e of edges) {\n\t\t\tmin = Math.min(min, lo(e));\n\t\t\tmax = Math.max(max, hi(e));\n\t\t}\n\t\tconst size = max > min ? (max - min) / bandCount : 1;\n\t\tconst band = (v: number) => Math.min(bandCount - 1, Math.max(0, Math.floor((v - min) / size)));\n\t\tconst bands: number[][] = Array.from({ length: bandCount }, () => []);\n\t\tedges.forEach((e, j) => {\n\t\t\tfor (let k = band(lo(e)); k <= band(hi(e)); k++) bands[k].push(j);\n\t\t});\n\t\treturn (v: number) => bands[band(v)];\n\t};\n\tconst bandsFor = new Map<boolean, (v: number) => number[]>();\n\tfor (let i = 0; i < edges.length; i++) {\n\t\tconst edge = edges[i];\n\t\t// Cast the ray across the edge rather than along it: rotating by −90° keeps windings\n\t\t// and turns a mostly horizontal edge into a mostly vertical one.\n\t\tconst horizontal = Math.abs(edge.b.y - edge.a.y) < Math.abs(edge.b.x - edge.a.x);\n\t\tconst tx = (v: Vertex) => (horizontal ? v.y : v.x);\n\t\tconst ty = (v: Vertex) => (horizontal ? -v.x : v.y);\n\t\tconst ax = tx(edge.a);\n\t\tconst ay = ty(edge.a);\n\t\tconst bx = tx(edge.b);\n\t\tconst by = ty(edge.b);\n\t\tconst mx = (ax + bx) / 2;\n\t\tconst my = (ay + by) / 2;\n\t\tconst winding = new Array(count).fill(0);\n\t\tif (!bandsFor.has(horizontal)) bandsFor.set(horizontal, bandIndex(horizontal));\n\t\tfor (const j of bandsFor.get(horizontal)!(my)) {\n\t\t\tif (j === i) continue;\n\t\t\tconst other = edges[j];\n\t\t\tconst oax = tx(other.a);\n\t\t\tconst oay = ty(other.a);\n\t\t\tconst obx = tx(other.b);\n\t\t\tconst oby = ty(other.b);\n\t\t\tlet dir = 0;\n\t\t\tif (oay <= my && oby > my) dir = 1;\n\t\t\telse if (oby <= my && oay > my) dir = -1;\n\t\t\telse continue;\n\t\t\tconst x = oax + ((my - oay) / (oby - oay)) * (obx - oax);\n\t\t\tif (x <= mx) continue;\n\t\t\tfor (let k = 0; k < count; k++) {\n\t\t\t\tif (other.mult[k] !== 0) winding[k] += dir * other.mult[k];\n\t\t\t}\n\t\t}\n\t\t// The ray starts just past the midpoint on the +x side, which is the right of the\n\t\t// edge when it points up (dy > 0) and its left otherwise.\n\t\tconst sideIsRight = by > ay;\n\t\tconst r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);\n\t\tright.push(r);\n\t\tleft.push(r.map((w, k) => w + edge.mult[k]));\n\t}\n\treturn { edges, right, left, fillRules: operands.map((o) => o.fillRule), eps };\n}\n\nfunction insideFlags(windings: number[], fillRules: PolygonFillRule[]): boolean[] {\n\treturn windings.map((w, k) => (fillRules[k] === 'evenodd' ? (w & 1) === 1 : w !== 0));\n}\n\n// ---------------------------------------------------------------------------\n// Ring extraction\n// ---------------------------------------------------------------------------\n\ninterface DirectedEdge {\n\tfrom: Vertex;\n\tto: Vertex;\n\tangle: number;\n}\n\n/** Traces the kept directed edges (result on the left) into rings. */\nfunction traceRings(directed: DirectedEdge[]): Vertex[][] {\n\tconst outgoing = new Map<number, DirectedEdge[]>();\n\tfor (const e of directed) {\n\t\tconst list = outgoing.get(e.from.id);\n\t\tif (list) list.push(e);\n\t\telse outgoing.set(e.from.id, [e]);\n\t}\n\tconst TWO_PI = Math.PI * 2;\n\tconst next = (incoming: DirectedEdge): DirectedEdge | undefined => {\n\t\tconst candidates = outgoing.get(incoming.to.id);\n\t\tif (!candidates) return undefined;\n\t\tconst back = Math.atan2(incoming.from.y - incoming.to.y, incoming.from.x - incoming.to.x);\n\t\tlet best: DirectedEdge | undefined;\n\t\tlet bestTurn = Infinity;\n\t\tfor (const c of candidates) {\n\t\t\t// Clockwise sweep from the reversed incoming edge: the first hit is the sharpest\n\t\t\t// left turn, which keeps the result region on the left and splits rings at\n\t\t\t// vertices they merely touch.\n\t\t\tlet turn = back - c.angle;\n\t\t\tturn -= Math.floor(turn / TWO_PI) * TWO_PI;\n\t\t\tif (turn <= 1e-15) turn = TWO_PI;\n\t\t\tif (turn < bestTurn) {\n\t\t\t\tbestTurn = turn;\n\t\t\t\tbest = c;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst used = new Set<DirectedEdge>();\n\tconst rings: Vertex[][] = [];\n\tfor (const start of directed) {\n\t\tif (used.has(start)) continue;\n\t\tconst ring: Vertex[] = [];\n\t\tlet edge: DirectedEdge | undefined = start;\n\t\tlet guard = directed.length + 1;\n\t\twhile (edge && !used.has(edge) && guard-- > 0) {\n\t\t\tused.add(edge);\n\t\t\tring.push(edge.from);\n\t\t\tedge = next(edge);\n\t\t}\n\t\tif (ring.length >= 3) rings.push(ring);\n\t}\n\treturn rings;\n}\n\n/** Removes vertices that lie on the straight line between their neighbours. */\nfunction removeCollinear(ring: Point[], eps: number): Point[] {\n\tlet points = ring;\n\tlet changed = true;\n\twhile (changed && points.length > 3) {\n\t\tchanged = false;\n\t\tconst out: Point[] = [];\n\t\tconst n = points.length;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst prev = out.length > 0 ? out[out.length - 1] : points[(i - 1 + n) % n];\n\t\t\tconst cur = points[i];\n\t\t\tconst nxt = points[(i + 1) % n];\n\t\t\tconst dx = nxt[0] - prev[0];\n\t\t\tconst dy = nxt[1] - prev[1];\n\t\t\tconst len = Math.hypot(dx, dy);\n\t\t\tconst cross = (cur[0] - prev[0]) * dy - (cur[1] - prev[1]) * dx;\n\t\t\tconst dot = (cur[0] - prev[0]) * dx + (cur[1] - prev[1]) * dy;\n\t\t\tif (len > 0 && Math.abs(cross) / len <= eps && dot > 0 && dot < len * len) {\n\t\t\t\tchanged = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push(cur);\n\t\t}\n\t\tif (out.length < 3) return out;\n\t\tpoints = out;\n\t}\n\treturn points;\n}\n\nfunction assemble(rings: Vertex[][], eps: number): MultiPolygon {\n\tconst outers: { ring: Point[]; area: number }[] = [];\n\tconst holes: { ring: Point[]; area: number }[] = [];\n\tconst minArea = eps * eps;\n\tfor (const vertices of rings) {\n\t\tconst ring = removeCollinear(\n\t\t\tvertices.map((v) => [v.x, v.y] as Point),\n\t\t\teps\n\t\t);\n\t\tif (ring.length < 3) continue;\n\t\tconst area = ringSignedArea(ring);\n\t\tif (Math.abs(area) <= minArea) continue;\n\t\t(area > 0 ? outers : holes).push({ ring, area: Math.abs(area) });\n\t}\n\touters.sort((a, b) => b.area - a.area);\n\tconst polygons: PolygonWithHoles[] = outers.map((o) => [o.ring]);\n\tfor (const hole of holes) {\n\t\t// Sample the midpoint of a hole edge: overlay edges are unique, so it is not on an\n\t\t// outer ring's boundary.\n\t\tconst [p, q] = [hole.ring[0], hole.ring[1]];\n\t\tconst sample: Point = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];\n\t\tlet owner = -1;\n\t\tfor (let i = outers.length - 1; i >= 0; i--) {\n\t\t\t// Iterating smallest-first finds the tightest container.\n\t\t\tif (outers[i].area >= hole.area && pointInRings(sample, [outers[i].ring], 'evenodd')) {\n\t\t\t\towner = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (owner >= 0) polygons[owner].push(hole.ring);\n\t}\n\treturn polygons;\n}\n\nfunction extract(overlay: Overlay, isInside: (flags: boolean[]) => boolean): MultiPolygon {\n\tconst directed: DirectedEdge[] = [];\n\toverlay.edges.forEach((edge, i) => {\n\t\tconst l = isInside(insideFlags(overlay.left[i], overlay.fillRules));\n\t\tconst r = isInside(insideFlags(overlay.right[i], overlay.fillRules));\n\t\tif (l === r) return;\n\t\tconst [from, to] = l ? [edge.a, edge.b] : [edge.b, edge.a];\n\t\tdirected.push({ from, to, angle: Math.atan2(to.y - from.y, to.x - from.x) });\n\t});\n\treturn assemble(traceRings(directed), overlay.eps);\n}\n\nfunction predicate(operation: PolygonBooleanOperation): (flags: boolean[]) => boolean {\n\tswitch (operation) {\n\t\tcase 'union':\n\t\t\treturn (f) => f.some(Boolean);\n\t\tcase 'intersection':\n\t\t\treturn (f) => f.length > 0 && f.every(Boolean);\n\t\tcase 'difference':\n\t\t\treturn (f) => f[0] === true && !f.slice(1).some(Boolean);\n\t\tcase 'xor':\n\t\t\treturn (f) => f.filter(Boolean).length % 2 === 1;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Public operations\n// ---------------------------------------------------------------------------\n\n/**\n * Applies a boolean operation to any number of operands.\n * - `union`: covered by any operand.\n * - `intersection`: covered by every operand.\n * - `difference`: the first operand minus all others.\n * - `xor`: covered by an odd number of operands (Exclude).\n *\n * Returns simple rings: outer rings have a positive signed area, holes a negative one.\n * Rings may touch at vertices but never cross.\n */\nexport function polygonBoolean(\n\toperation: PolygonBooleanOperation,\n\toperands: PolygonInput[],\n\toptions: PolygonBooleanOptions = {}\n): MultiPolygon {\n\tif (operands.length === 0) return [];\n\treturn extract(buildOverlay(operands, options), predicate(operation));\n}\n\nexport const unionPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('union', [a, b], options);\nexport const intersectPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('intersection', [a, b], options);\nexport const subtractPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('difference', [a, b], options);\nexport const xorPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('xor', [a, b], options);\n\n/**\n * Splits the overlay of the operands into its distinct regions (Freeform's Divide): every\n * connected area covered by the same set of operands becomes one piece. Pieces are ordered\n * by their operand set, then by size (largest first).\n */\nexport function dividePolygons(operands: PolygonInput[], options: PolygonBooleanOptions = {}): PolygonPiece[] {\n\tif (operands.length === 0) return [];\n\tconst overlay = buildOverlay(operands, options);\n\tconst signatures = new Map<string, number[]>();\n\tconst note = (windings: number[]) => {\n\t\tconst flags = insideFlags(windings, overlay.fillRules);\n\t\tif (!flags.some(Boolean)) return;\n\t\tconst key = flags.map((f) => (f ? '1' : '0')).join('');\n\t\tif (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => (f ? [k] : [])));\n\t};\n\toverlay.edges.forEach((_, i) => {\n\t\tnote(overlay.left[i]);\n\t\tnote(overlay.right[i]);\n\t});\n\tconst pieces: PolygonPiece[] = [];\n\tconst keys = [...signatures.keys()].sort();\n\tfor (const key of keys) {\n\t\tconst polygons = extract(overlay, (flags) => flags.map((f) => (f ? '1' : '0')).join('') === key);\n\t\tpolygons.sort((a, b) => polygonArea(b) - polygonArea(a));\n\t\tfor (const polygon of polygons) pieces.push({ polygon, operands: signatures.get(key)! });\n\t}\n\treturn pieces;\n}\n","/**\n * Least-squares cubic Bézier fitting of point sequences (Schneider-style: fit one cubic with\n * fixed end tangents, reparameterise with Newton steps, split at the worst point), with\n * corner preservation by a turning-angle test.\n *\n * Portions ported from vectorcraft-main/crates/pathops/src/fit.rs and\n * vectorcraft-main/crates/pathops/src/edit.rs (MIT OR Apache-2.0, © ArtCraft Team and\n * contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from './types.js';\n\n/** Same as `formatNumber` in shapeGeometry (kept local to avoid an import cycle). */\nfunction formatNumber(value: number, precision: number): string {\n\tconst factor = 10 ** precision;\n\tconst rounded = Math.round(value * factor) / factor;\n\treturn Object.is(rounded, -0) ? '0' : String(rounded);\n}\n\n/** One cubic Bézier segment. */\nexport interface CubicSegment {\n\tp0: Point;\n\tp1: Point;\n\tp2: Point;\n\tp3: Point;\n}\n\nexport interface FitCubicsOptions {\n\t/**\n\t * Vertices where the polyline turns by more than this many degrees stay sharp corners\n\t * (default 30). Use 180 to smooth through every vertex.\n\t */\n\tcornerAngleDeg?: number;\n\t/** Treat the points as a closed ring (the last point connects back to the first). */\n\tclosed?: boolean;\n}\n\n/** Maximum Newton reparameterisation rounds per fit. */\nconst MAX_REPARAM = 6;\n\ntype Vec = [number, number];\n\nconst sub = (a: Point, b: Point): Vec => [a[0] - b[0], a[1] - b[1]];\nconst add = (a: Point, v: Vec, s = 1): Vec => [a[0] + v[0] * s, a[1] + v[1] * s];\nconst dot = (a: Vec, b: Vec) => a[0] * b[0] + a[1] * b[1];\nconst dist = (a: Point, b: Point) => Math.hypot(a[0] - b[0], a[1] - b[1]);\n\nfunction unit(v: Vec): Vec | null {\n\tconst l = Math.hypot(v[0], v[1]);\n\treturn l > 1e-12 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;\n}\n\nfunction evalCubic(c: CubicSegment, t: number): Vec {\n\tconst s = 1 - t;\n\tconst b0 = s * s * s;\n\tconst b1 = 3 * s * s * t;\n\tconst b2 = 3 * s * t * t;\n\tconst b3 = t * t * t;\n\treturn [\n\t\tb0 * c.p0[0] + b1 * c.p1[0] + b2 * c.p2[0] + b3 * c.p3[0],\n\t\tb0 * c.p0[1] + b1 * c.p1[1] + b2 * c.p2[1] + b3 * c.p3[1]\n\t];\n}\n\n/** Evaluates a cubic segment at `t` ∈ [0, 1]. */\nexport function cubicPointAt(c: CubicSegment, t: number): Point {\n\treturn evalCubic(c, t);\n}\n\nfunction dedup(points: readonly Point[]): Point[] {\n\tconst out: Point[] = [];\n\tfor (const p of points) {\n\t\tif (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;\n\t\tif (out.length === 0 || dist(out[out.length - 1], p) > 1e-12) out.push(p);\n\t}\n\treturn out;\n}\n\n/** Chord-length parameters (0 to 1), one per point. */\nfunction chordParams(pts: Point[]): number[] {\n\tconst u = [0];\n\tlet acc = 0;\n\tfor (let i = 1; i < pts.length; i++) {\n\t\tacc += dist(pts[i - 1], pts[i]);\n\t\tu.push(acc);\n\t}\n\treturn acc > 0 ? u.map((x) => x / acc) : u;\n}\n\n/** Solves the 2×2 normal equations for the handle lengths along the fixed tangents. */\nfunction generate(pts: Point[], u: number[], t0: Vec, t1: Vec): CubicSegment {\n\tconst p0 = pts[0];\n\tconst p3 = pts[pts.length - 1];\n\tconst dirIn: Vec = [-t1[0], -t1[1]];\n\tlet c00 = 0;\n\tlet c01 = 0;\n\tlet c11 = 0;\n\tlet x0 = 0;\n\tlet x1 = 0;\n\tfor (let i = 0; i < pts.length; i++) {\n\t\tconst t = u[i];\n\t\tconst s = 1 - t;\n\t\tconst b0 = s * s * s;\n\t\tconst b1 = 3 * s * s * t;\n\t\tconst b2 = 3 * s * t * t;\n\t\tconst b3 = t * t * t;\n\t\tconst a0: Vec = [t0[0] * b1, t0[1] * b1];\n\t\tconst a1: Vec = [dirIn[0] * b2, dirIn[1] * b2];\n\t\tc00 += dot(a0, a0);\n\t\tc01 += dot(a0, a1);\n\t\tc11 += dot(a1, a1);\n\t\tconst tmp: Vec = [\n\t\t\tpts[i][0] - (p0[0] * (b0 + b1) + p3[0] * (b2 + b3)),\n\t\t\tpts[i][1] - (p0[1] * (b0 + b1) + p3[1] * (b2 + b3))\n\t\t];\n\t\tx0 += dot(a0, tmp);\n\t\tx1 += dot(a1, tmp);\n\t}\n\tconst det = c00 * c11 - c01 * c01;\n\tconst seg = dist(p0, p3);\n\tconst eps = 1e-6 * seg;\n\tlet al = seg / 3;\n\tlet ar = seg / 3;\n\tif (Math.abs(det) > 1e-12) {\n\t\tal = (x0 * c11 - x1 * c01) / det;\n\t\tar = (c00 * x1 - c01 * x0) / det;\n\t}\n\tif (!Number.isFinite(al) || !Number.isFinite(ar) || al < eps || ar < eps) {\n\t\tal = seg / 3;\n\t\tar = seg / 3;\n\t}\n\t// Guard against wild handles on nearly degenerate data.\n\tconst cap = seg * 4 + 1e-9;\n\tal = Math.min(al, cap);\n\tar = Math.min(ar, cap);\n\treturn { p0, p1: add(p0, t0, al), p2: add(p3, dirIn, ar), p3 };\n}\n\n/** Largest distance between a point and the curve at its parameter, and that point's index. */\nfunction maxError(pts: Point[], c: CubicSegment, u: number[]): [number, number] {\n\tlet e = 0;\n\tlet idx = pts.length >> 1;\n\tfor (let i = 0; i < pts.length; i++) {\n\t\tconst d = dist(evalCubic(c, u[i]), pts[i]);\n\t\tif (d > e) {\n\t\t\te = d;\n\t\t\tidx = i;\n\t\t}\n\t}\n\treturn [e, idx];\n}\n\n/** One Newton–Raphson step towards the parameter of the point on `c` nearest `p`. */\nfunction newton(c: CubicSegment, p: Point, t: number): number {\n\tconst s = 1 - t;\n\tconst q = evalCubic(c, t);\n\tconst d1: Vec = [\n\t\t3 * (s * s * (c.p1[0] - c.p0[0]) + 2 * s * t * (c.p2[0] - c.p1[0]) + t * t * (c.p3[0] - c.p2[0])),\n\t\t3 * (s * s * (c.p1[1] - c.p0[1]) + 2 * s * t * (c.p2[1] - c.p1[1]) + t * t * (c.p3[1] - c.p2[1]))\n\t];\n\tconst d2: Vec = [\n\t\t6 * (s * (c.p2[0] - 2 * c.p1[0] + c.p0[0]) + t * (c.p3[0] - 2 * c.p2[0] + c.p1[0])),\n\t\t6 * (s * (c.p2[1] - 2 * c.p1[1] + c.p0[1]) + t * (c.p3[1] - 2 * c.p2[1] + c.p1[1]))\n\t];\n\tconst diff: Vec = [q[0] - p[0], q[1] - p[1]];\n\tconst num = dot(diff, d1);\n\tconst den = dot(d1, d1) + dot(diff, d2);\n\tif (Math.abs(den) < 1e-12) return t;\n\tconst nt = t - num / den;\n\treturn Number.isFinite(nt) ? Math.min(1, Math.max(0, nt)) : t;\n}\n\n/**\n * Best single cubic through `pts` with the given unit end tangents (`t1` points forward at\n * the end), starting from parameters `u`. Returns the curve, its max error and the index of\n * the worst point.\n */\nfunction fitSingleFrom(pts: Point[], u: number[], t0: Vec, t1: Vec): [CubicSegment, number, number] {\n\tlet best = generate(pts, u, t0, t1);\n\tlet [bestErr, bestSplit] = maxError(pts, best, u);\n\tconst params = u.slice();\n\tfor (let round = 0; round < MAX_REPARAM; round++) {\n\t\tfor (let i = 0; i < params.length; i++) params[i] = newton(best, pts[i], params[i]);\n\t\tconst c = generate(pts, params, t0, t1);\n\t\tconst [e, s] = maxError(pts, c, params);\n\t\tif (e < bestErr) {\n\t\t\tbest = c;\n\t\t\tbestErr = e;\n\t\t\tbestSplit = s;\n\t\t} else break;\n\t}\n\treturn [best, bestErr, bestSplit];\n}\n\n/**\n * The best single cubic from the first to the last of `points`, leaving along the unit tangent\n * `t0` and arriving along `t1` (forward), with the points starting at parameters `params`\n * (refined by Newton steps). Returns the curve and its largest point error.\n */\nexport function fitCubicWithTangents(points: readonly Point[], params: readonly number[], t0: Point, t1: Point): { curve: CubicSegment; error: number } {\n\tconst [curve, error] = fitSingleFrom(points.slice(), params.slice(), [t0[0], t0[1]], [t1[0], t1[1]]);\n\treturn { curve, error };\n}\n\nfunction fitRec(pts: Point[], t0: Vec, t1: Vec, tol: number, out: CubicSegment[], depth: number) {\n\tconst n = pts.length;\n\tif (n === 2) {\n\t\tconst d = dist(pts[0], pts[1]) / 3;\n\t\tout.push({ p0: pts[0], p1: add(pts[0], t0, d), p2: add(pts[1], t1, -d), p3: pts[1] });\n\t\treturn;\n\t}\n\tconst [c, err, splitAt] = fitSingleFrom(pts, chordParams(pts), t0, t1);\n\tif (err <= tol || depth > 40) {\n\t\tout.push(c);\n\t\treturn;\n\t}\n\t// Split at the worst point with a centre tangent estimated from its neighbours.\n\tconst split = Math.min(n - 2, Math.max(1, splitAt));\n\tconst tc = unit(sub(pts[split + 1], pts[split - 1])) ?? t0;\n\tfitRec(pts.slice(0, split + 1), t0, tc, tol, out, depth + 1);\n\tfitRec(pts.slice(split), tc, t1, tol, out, depth + 1);\n}\n\n/**\n * Unit tangent at `a` estimated from the parabola through `a`, `b`, `c` (chord-length\n * parameters): second-order accurate, so polylines sampled from curves get their true end\n * direction rather than the first chord's.\n */\nfunction tangentFrom(a: Point, b: Point, c: Point | undefined): Vec | null {\n\tconst chord = unit(sub(b, a));\n\tif (!c) return chord;\n\tconst d1 = dist(a, b);\n\tconst d2 = dist(b, c);\n\tif (!(d1 > 0) || !(d2 > 0)) return chord;\n\tconst ka = -(2 * d1 + d2) / (d1 * (d1 + d2));\n\tconst kb = (d1 + d2) / (d1 * d2);\n\tconst kc = -d1 / (d2 * (d1 + d2));\n\tconst est = unit([ka * a[0] + kb * b[0] + kc * c[0], ka * a[1] + kb * b[1] + kc * c[1]]);\n\t// Fall back to the chord when the estimate swings too far (noisy or sharp data).\n\treturn est && chord && dot(est, chord) > Math.cos(Math.PI / 4) ? est : chord;\n}\n\n/**\n * Fits a G1-continuous chain of cubics through `points` (an open run) with the unit\n * tangents `t0` (leaving the first point) and `t1` (arriving at the last point, forward);\n * both are estimated from the end points when omitted. Every point lies within `tolerance`\n * of the curve at its fitted parameter.\n */\nexport function fitCubicRun(points: readonly Point[], tolerance: number, t0?: Point, t1?: Point): CubicSegment[] {\n\tconst pts = dedup(points);\n\tconst out: CubicSegment[] = [];\n\tif (pts.length < 2) return out;\n\tconst n = pts.length;\n\tconst start = (t0 && unit([t0[0], t0[1]])) ?? tangentFrom(pts[0], pts[1], pts[2]) ?? [1, 0];\n\tconst back = tangentFrom(pts[n - 1], pts[n - 2], pts[n - 3]);\n\tconst end = (t1 && unit([t1[0], t1[1]])) ?? (back ? ([-back[0], -back[1]] as Vec) : [1, 0]);\n\tfitRec(pts, start, end, Math.max(tolerance, 1e-9), out, 0);\n\treturn out;\n}\n\n/** Turning angle in degrees between the directions `a`→`b` and `b`→`c`. */\nfunction turnDeg(a: Point, b: Point, c: Point): number {\n\tconst u = unit(sub(b, a));\n\tconst v = unit(sub(c, b));\n\tif (!u || !v) return 0;\n\treturn (Math.acos(Math.min(1, Math.max(-1, dot(u, v)))) * 180) / Math.PI;\n}\n\n/**\n * Corner vertices of a polyline. The turn at each vertex is measured to the nearest\n * neighbours at least `span` away on each side (so sample noise smaller than the fitting\n * tolerance does not read as corners), and only the sharpest vertex of a neighbourhood is\n * kept. End points of open polylines are not included.\n */\nfunction findCorners(pts: Point[], closed: boolean, cornerAngle: number, span: number): number[] {\n\tconst n = pts.length;\n\tconst at = (i: number) => pts[closed ? ((i % n) + n) % n : i];\n\tconst reach = (i: number, step: 1 | -1): number | null => {\n\t\tfor (let k = 1; k < (closed ? n : Infinity); k++) {\n\t\t\tconst j = i + step * k;\n\t\t\tif (!closed && (j < 0 || j >= n)) return k > 1 ? j - step : null;\n\t\t\tif (dist(at(j), pts[i]) >= span) return j;\n\t\t}\n\t\treturn null;\n\t};\n\tconst turns = new Float64Array(n);\n\tconst windows: [number, number][] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\twindows.push([i, i]);\n\t\tif (!closed && (i === 0 || i === n - 1)) continue;\n\t\tconst prev = reach(i, -1);\n\t\tconst next = reach(i, 1);\n\t\tif (prev === null || next === null) continue;\n\t\twindows[i] = [prev, next];\n\t\tturns[i] = turnDeg(at(prev), pts[i], at(next));\n\t}\n\tconst corners: number[] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tif (!(turns[i] > cornerAngle)) continue;\n\t\tconst [lo, hi] = windows[i];\n\t\tlet sharpest = true;\n\t\tfor (let j = lo; j <= hi && sharpest; j++) {\n\t\t\tconst k = closed ? ((j % n) + n) % n : j;\n\t\t\tif (k === i || dist(pts[k], pts[i]) >= span) continue;\n\t\t\tif (turns[k] > turns[i] || (turns[k] === turns[i] && k < i)) sharpest = false;\n\t\t}\n\t\tif (sharpest) corners.push(i);\n\t}\n\treturn corners;\n}\n\n/**\n * Points to fit for a run of polyline vertices: the vertices, plus extra samples along edges\n * much longer than the run's typical edge so straight stretches stay straight.\n */\nfunction densify(run: Point[]): Point[] {\n\tif (run.length < 3) return run;\n\tconst lengths: number[] = [];\n\tfor (let i = 1; i < run.length; i++) lengths.push(dist(run[i - 1], run[i]));\n\tconst sorted = [...lengths].sort((a, b) => a - b);\n\tconst median = sorted[sorted.length >> 1];\n\tif (!(median > 0)) return run;\n\tconst out: Point[] = [run[0]];\n\tfor (let i = 1; i < run.length; i++) {\n\t\tconst len = lengths[i - 1];\n\t\tif (len > 2 * median) {\n\t\t\tconst steps = Math.min(64, Math.ceil(len / median));\n\t\t\tconst a = run[i - 1];\n\t\t\tconst b = run[i];\n\t\t\tfor (let k = 1; k < steps; k++) {\n\t\t\t\tconst t = k / steps;\n\t\t\t\tout.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);\n\t\t\t}\n\t\t}\n\t\tout.push(run[i]);\n\t}\n\treturn out;\n}\n\n/**\n * Fits cubic Bézier segments to a polyline (`closed` for a ring), keeping vertices that turn\n * by more than `cornerAngleDeg` (default 30°) as sharp corners. Each stretch between corners\n * is fitted Schneider-style so that every input point is within `tolerance` of the result.\n *\n * Returns the segments in order (consecutive segments share end points; a closed result ends\n * where it starts). Fewer than two distinct points give an empty array.\n */\nexport function fitCubics(points: readonly Point[], tolerance: number, options: FitCubicsOptions = {}): CubicSegment[] {\n\tlet pts = dedup(points);\n\tconst closed = options.closed === true;\n\tif (closed && pts.length > 1 && dist(pts[0], pts[pts.length - 1]) <= 1e-12) pts = pts.slice(0, -1);\n\tif (pts.length < 2) return [];\n\tconst tol = Math.max(tolerance, 1e-6);\n\tconst cornerAngle = options.cornerAngleDeg ?? 30;\n\tconst n = pts.length;\n\tconst span = 4 * tol;\n\n\tif (!closed || n < 3) {\n\t\tconst corners = [0, ...(n > 2 ? findCorners(pts, false, cornerAngle, span) : []), n - 1];\n\t\tconst out: CubicSegment[] = [];\n\t\tfor (let k = 1; k < corners.length; k++) {\n\t\t\tconst run = pts.slice(corners[k - 1], corners[k] + 1);\n\t\t\tout.push(...fitCubicRun(densify(run), tol));\n\t\t}\n\t\tif (closed && n === 2) out.push(...fitCubicRun([pts[1], pts[0]], tol));\n\t\treturn out;\n\t}\n\n\tconst corners = findCorners(pts, true, cornerAngle, span);\n\tif (corners.length === 0) {\n\t\t// Smooth ring: start anywhere, with the same tangent leaving and arriving.\n\t\tconst ring = [...pts, pts[0]];\n\t\tconst t = unit(sub(pts[1], pts[n - 1])) ?? [1, 0];\n\t\t// (A central difference: already second-order accurate at the seam.)\n\t\treturn fitCubicRun(densify(ring), tol, t, t);\n\t}\n\tconst out: CubicSegment[] = [];\n\tfor (let k = 0; k < corners.length; k++) {\n\t\tconst from = corners[k];\n\t\tconst to = corners[(k + 1) % corners.length];\n\t\tconst run: Point[] = [];\n\t\tfor (let i = from; ; i = (i + 1) % n) {\n\t\t\trun.push(pts[i]);\n\t\t\tif (run.length > 1 && i === to) break;\n\t\t}\n\t\tout.push(...fitCubicRun(densify(run), tol));\n\t}\n\treturn out;\n}\n\n/** Whether a cubic is (within `tol`) the straight line from `p0` to `p3`. */\nexport function isStraightCubic(c: CubicSegment, tol: number): boolean {\n\tconst chord = sub(c.p3, c.p0);\n\tconst len = Math.hypot(chord[0], chord[1]);\n\tif (len < 1e-12) return dist(c.p1, c.p0) <= tol && dist(c.p2, c.p0) <= tol;\n\tconst dir: Vec = [chord[0] / len, chord[1] / len];\n\treturn [c.p1, c.p2].every((h) => {\n\t\tconst v = sub(h, c.p0);\n\t\tconst along = dot(v, dir);\n\t\tconst across = Math.abs(v[0] * dir[1] - v[1] * dir[0]);\n\t\treturn across <= tol && along >= -tol && along <= len + tol;\n\t});\n}\n\nexport interface FitPathDataOptions extends FitCubicsOptions {\n\t/** Fitting tolerance in path units (default 0.5). */\n\ttolerance?: number;\n\t/** Decimal places (default 3). */\n\tprecision?: number;\n\t/** Added to every point before formatting. */\n\toffset?: { x: number; y: number };\n\t/**\n\t * Keep the original polyline when fitting would need more segments than it has edges\n\t * (default `true`), so fitting never makes a path heavier.\n\t */\n\tnoWorse?: boolean;\n}\n\n/** SVG `d` for fitted segments (`L` for straight ones, `C` otherwise). */\nexport function cubicsToPathData(\n\tsegments: readonly CubicSegment[],\n\toptions: { precision?: number; offset?: { x: number; y: number }; closed?: boolean; straightTolerance?: number } = {}\n): string {\n\tif (segments.length === 0) return '';\n\tconst f = (n: number) => formatNumber(n, options.precision ?? 3);\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tconst pt = (p: Point) => `${f(p[0] + dx)} ${f(p[1] + dy)}`;\n\tconst straight = options.straightTolerance ?? 1e-3;\n\tlet d = `M${pt(segments[0].p0)}`;\n\tsegments.forEach((c, i) => {\n\t\tconst last = options.closed && i === segments.length - 1;\n\t\tif (isStraightCubic(c, straight)) {\n\t\t\tif (!last) d += `L${pt(c.p3)}`;\n\t\t} else d += `C${pt(c.p1)} ${pt(c.p2)} ${pt(c.p3)}`;\n\t});\n\treturn options.closed ? `${d}Z` : d;\n}\n\nfunction polylineToPathData(points: readonly Point[], closed: boolean, options: FitPathDataOptions): string {\n\tif (points.length < 2) return '';\n\tconst f = (n: number) => formatNumber(n, options.precision ?? 3);\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tlet d = `M${f(points[0][0] + dx)} ${f(points[0][1] + dy)}`;\n\tfor (let i = 1; i < points.length; i++) d += `L${f(points[i][0] + dx)} ${f(points[i][1] + dy)}`;\n\treturn closed ? `${d}Z` : d;\n}\n\n/**\n * Fits smooth curves to a polygon ring (closed by default) and returns SVG path data with\n * `C` curves for smooth stretches, `L` lines for straight ones and sharp corners kept.\n * Use it for boolean results of curved shapes or to smooth freehand strokes\n * (`{ closed: false }`, see `fitFreehandPath`).\n */\nexport function fitPathData(ring: readonly Point[], options: FitPathDataOptions = {}): string {\n\tconst closed = options.closed ?? true;\n\tconst tolerance = options.tolerance ?? 0.5;\n\tconst pts = dedup(ring);\n\tconst segments = fitCubics(pts, tolerance, { ...options, closed });\n\tif (segments.length === 0) return '';\n\tconst edges = closed ? pts.length : pts.length - 1;\n\tif (options.noWorse !== false) {\n\t\tconst straight = segments.filter((c) => isStraightCubic(c, tolerance * 0.02)).length;\n\t\t// Fitting only pays off when it removes vertices.\n\t\tif (segments.length >= edges && straight === segments.length) return polylineToPathData(pts, closed, options);\n\t\tif (segments.length > edges) return polylineToPathData(pts, closed, options);\n\t}\n\treturn cubicsToPathData(segments, {\n\t\tprecision: options.precision,\n\t\toffset: options.offset,\n\t\tclosed,\n\t\tstraightTolerance: tolerance * 0.02\n\t});\n}\n\n/**\n * Smooths a freehand stroke (pointer samples) into an open SVG path of cubic curves.\n * `tolerance` is the largest distance (in the points' units) a sample may be from the\n * curve (default 1); sharp turns above `cornerAngleDeg` (default 30) stay corners.\n */\nexport function fitFreehandPath(\n\tpoints: readonly Point[],\n\toptions: Omit<FitPathDataOptions, 'closed'> = {}\n): string {\n\treturn fitPathData(points, { tolerance: 1, ...options, closed: false });\n}\n","/**\n * Canvas gradient models, parsing, presets, and SVG defs generation.\n * Supports linear and radial gradients for shape fills, strokes, and freehand drawing.\n */\n\nexport interface GradientStop {\n\toffset: number; // 0 to 1\n\tcolor: string;\n\topacity?: number;\n}\n\nexport interface CanvasLinearGradient {\n\ttype: 'linear';\n\tangle?: number; // degrees clockwise, default 90 (top-to-bottom) or custom\n\tstops: GradientStop[];\n}\n\nexport interface CanvasRadialGradient {\n\ttype: 'radial';\n\tcx?: number; // 0 to 1 (default 0.5)\n\tcy?: number; // 0 to 1 (default 0.5)\n\tr?: number; // 0 to 1 (default 0.5)\n\tstops: GradientStop[];\n}\n\nexport type CanvasGradientDef = CanvasLinearGradient | CanvasRadialGradient;\n\nexport interface SvgGradientStop {\n\toffset: string;\n\tstopColor: string;\n\tstopOpacity?: number;\n}\n\nexport interface SvgGradientDef {\n\tid: string;\n\ttype: 'linear' | 'radial';\n\tattrs: Record<string, string | number>;\n\tstops: SvgGradientStop[];\n}\n\nexport const CANVAS_GRADIENT_PRESETS: Record<string, CanvasLinearGradient> = {\n\tsunset: {\n\t\ttype: 'linear',\n\t\tangle: 90,\n\t\tstops: [\n\t\t\t{ offset: 0, color: '#f97316' },\n\t\t\t{ offset: 1, color: '#ec4899' }\n\t\t]\n\t},\n\tocean: {\n\t\ttype: 'linear',\n\t\tangle: 90,\n\t\tstops: [\n\t\t\t{ offset: 0, color: '#06b6d4' },\n\t\t\t{ offset: 1, color: '#3b82f6' }\n\t\t]\n\t},\n\taurora: {\n\t\ttype: 'linear',\n\t\tangle: 90,\n\t\tstops: [\n\t\t\t{ offset: 0, color: '#10b981' },\n\t\t\t{ offset: 1, color: '#6366f1' }\n\t\t]\n\t},\n\tneon: {\n\t\ttype: 'linear',\n\t\tangle: 90,\n\t\tstops: [\n\t\t\t{ offset: 0, color: '#a855f7' },\n\t\t\t{ offset: 1, color: '#ec4899' }\n\t\t]\n\t},\n\tamber: {\n\t\ttype: 'linear',\n\t\tangle: 90,\n\t\tstops: [\n\t\t\t{ offset: 0, color: '#f59e0b' },\n\t\t\t{ offset: 1, color: '#ef4444' }\n\t\t]\n\t},\n\tcosmic: {\n\t\ttype: 'linear',\n\t\tangle: 90,\n\t\tstops: [\n\t\t\t{ offset: 0, color: '#3b82f6' },\n\t\t\t{ offset: 0.5, color: '#8b5cf6' },\n\t\t\t{ offset: 1, color: '#ec4899' }\n\t\t]\n\t}\n};\n\n/** Whether an arbitrary value matches a CanvasGradientDef object. */\nexport function isCanvasGradient(value: unknown): value is CanvasGradientDef {\n\tif (!value || typeof value !== 'object') return false;\n\tconst val = value as Partial<CanvasGradientDef>;\n\tif (val.type !== 'linear' && val.type !== 'radial') return false;\n\tif (!Array.isArray(val.stops)) return false;\n\treturn true;\n}\n\n/**\n * Splits CSS function arguments respecting nested parentheses.\n * e.g. \"rgba(255, 0, 0, 0.5) 0%, #00f 100%\" -> [\"rgba(255, 0, 0, 0.5) 0%\", \"#00f 100%\"]\n */\nfunction splitCssArgs(str: string): string[] {\n\tconst parts: string[] = [];\n\tlet depth = 0;\n\tlet current = '';\n\tfor (let i = 0; i < str.length; i++) {\n\t\tconst char = str[i];\n\t\tif (char === '(') depth++;\n\t\telse if (char === ')') depth = Math.max(0, depth - 1);\n\t\tif (char === ',' && depth === 0) {\n\t\t\tif (current.trim()) parts.push(current.trim());\n\t\t\tcurrent = '';\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\tif (current.trim()) parts.push(current.trim());\n\treturn parts;\n}\n\n/** Parses CSS angle strings like \"90deg\", \"0.25turn\", \"1.57rad\" or named directions like \"to bottom\" */\nfunction parseCssAngle(val: string): number {\n\tconst trimmed = val.trim().toLowerCase();\n\tif (trimmed.endsWith('deg')) {\n\t\treturn parseFloat(trimmed);\n\t}\n\tif (trimmed.endsWith('turn')) {\n\t\treturn parseFloat(trimmed) * 360;\n\t}\n\tif (trimmed.endsWith('rad')) {\n\t\treturn (parseFloat(trimmed) * 180) / Math.PI;\n\t}\n\tif (trimmed.startsWith('to ')) {\n\t\tconst dir = trimmed.slice(3).trim();\n\t\tif (dir === 'bottom') return 180;\n\t\tif (dir === 'top') return 0;\n\t\tif (dir === 'right') return 90;\n\t\tif (dir === 'left') return 270;\n\t\tif (dir === 'bottom right' || dir === 'right bottom') return 135;\n\t\tif (dir === 'bottom left' || dir === 'left bottom') return 225;\n\t\tif (dir === 'top right' || dir === 'right top') return 45;\n\t\tif (dir === 'top left' || dir === 'left top') return 315;\n\t}\n\tconst n = parseFloat(trimmed);\n\treturn Number.isFinite(n) ? n : 180;\n}\n\n/**\n * Parses CSS linear-gradient or radial-gradient strings into CanvasGradientDef.\n * Supports forms like:\n * - linear-gradient(90deg, #ff0000 0%, #0000ff 100%)\n * - linear-gradient(to right, red, blue)\n * - radial-gradient(circle, red 0%, blue 100%)\n */\nexport function parseCssGradient(css: string): CanvasGradientDef | null {\n\tconst trimmed = css.trim();\n\tconst linearMatch = trimmed.match(/^linear-gradient\\s*\\((.*)\\)$/is);\n\tif (linearMatch) {\n\t\tconst rawArgs = splitCssArgs(linearMatch[1]);\n\t\tif (rawArgs.length === 0) return null;\n\t\tlet angle = 180; // default in CSS linear-gradient without direction is to bottom (180deg)\n\t\tlet stopStartIndex = 0;\n\n\t\tconst first = rawArgs[0].trim().toLowerCase();\n\t\tif (first.endsWith('deg') || first.endsWith('turn') || first.endsWith('rad') || first.startsWith('to ')) {\n\t\t\tangle = parseCssAngle(first);\n\t\t\tstopStartIndex = 1;\n\t\t}\n\n\t\tconst stopStrings = rawArgs.slice(stopStartIndex);\n\t\tif (stopStrings.length === 0) return null;\n\t\tconst stops = parseStopStrings(stopStrings);\n\t\tif (stops.length === 0) return null;\n\t\treturn {\n\t\t\ttype: 'linear',\n\t\t\tangle,\n\t\t\tstops\n\t\t};\n\t}\n\n\tconst radialMatch = trimmed.match(/^radial-gradient\\s*\\((.*)\\)$/is);\n\tif (radialMatch) {\n\t\tconst rawArgs = splitCssArgs(radialMatch[1]);\n\t\tif (rawArgs.length === 0) return null;\n\t\tlet stopStartIndex = 0;\n\t\tconst first = rawArgs[0].trim().toLowerCase();\n\t\tif (first.startsWith('circle') || first.startsWith('ellipse') || first.includes('at ')) {\n\t\t\tstopStartIndex = 1;\n\t\t}\n\t\tconst stopStrings = rawArgs.slice(stopStartIndex);\n\t\tif (stopStrings.length === 0) return null;\n\t\tconst stops = parseStopStrings(stopStrings);\n\t\tif (stops.length === 0) return null;\n\t\treturn {\n\t\t\ttype: 'radial',\n\t\t\tcx: 0.5,\n\t\t\tcy: 0.5,\n\t\t\tr: 0.5,\n\t\t\tstops\n\t\t};\n\t}\n\n\treturn null;\n}\n\nfunction parseStopStrings(stopStrings: string[]): GradientStop[] {\n\tconst count = stopStrings.length;\n\treturn stopStrings.map((str, idx) => {\n\t\tconst parts = str.trim().split(/\\s+(?=[0-9%])/);\n\t\tconst color = parts[0];\n\t\tlet offset = count === 1 ? 0 : idx / (count - 1);\n\t\tif (parts.length > 1) {\n\t\t\tconst posStr = parts[1].trim();\n\t\t\tif (posStr.endsWith('%')) {\n\t\t\t\toffset = parseFloat(posStr) / 100;\n\t\t\t} else {\n\t\t\t\tconst val = parseFloat(posStr);\n\t\t\t\tif (Number.isFinite(val)) offset = val;\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\toffset: Math.min(1, Math.max(0, offset)),\n\t\t\tcolor\n\t\t};\n\t});\n}\n\n/**\n * Resolves a paint value (which could be a CSS color, preset name, CSS gradient string, or CanvasGradientDef)\n * into either a CanvasGradientDef (if gradient) or null (if solid color or invalid).\n */\nexport function resolveCanvasGradient(value: unknown): CanvasGradientDef | null {\n\tif (!value) return null;\n\tif (isCanvasGradient(value)) return value;\n\tif (typeof value === 'string') {\n\t\tconst lower = value.trim().toLowerCase();\n\t\tif (CANVAS_GRADIENT_PRESETS[lower]) {\n\t\t\treturn CANVAS_GRADIENT_PRESETS[lower];\n\t\t}\n\t\tif (lower.startsWith('linear-gradient') || lower.startsWith('radial-gradient')) {\n\t\t\treturn parseCssGradient(value);\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Converts degrees clockwise (CSS angle: 0 = top, 90 = right, 180 = bottom, 270 = left)\n * to SVG linearGradient vector coordinates (x1, y1, x2, y2) normalized [0, 1].\n */\nexport function angleToSvgLinearCoords(angleDeg: number = 180): { x1: number; y1: number; x2: number; y2: number } {\n\t// Standard CSS angle: 0deg points UP (towards -Y in standard canvas coordinates)\n\t// Convert to standard mathematical angle in radians (0 at +X, counterclockwise)\n\t// CSS 0deg = Math 90deg (up)\n\t// CSS angle A -> Math theta = 90 - A\n\tconst rad = ((90 - angleDeg) * Math.PI) / 180;\n\t// Vector direction\n\tconst dx = Math.cos(rad);\n\tconst dy = -Math.sin(rad); // invert for SVG/screen y-down coords\n\n\t// Project from center (0.5, 0.5) to edges\n\tconst half = 0.5;\n\treturn {\n\t\tx1: Math.round((half - dx * half) * 1000) / 1000,\n\t\ty1: Math.round((half - dy * half) * 1000) / 1000,\n\t\tx2: Math.round((half + dx * half) * 1000) / 1000,\n\t\ty2: Math.round((half + dy * half) * 1000) / 1000\n\t};\n}\n\n/**\n * Converts a CanvasGradientDef to an SvgGradientDef descriptor ready for rendering `<linearGradient>` / `<radialGradient>`.\n */\nexport function gradientToSvgDef(gradient: CanvasGradientDef, id: string): SvgGradientDef {\n\tconst stops: SvgGradientStop[] = (gradient.stops ?? []).map((s) => ({\n\t\toffset: `${Math.round(s.offset * 1000) / 10}%`,\n\t\tstopColor: s.color,\n\t\tstopOpacity: s.opacity\n\t}));\n\n\tif (gradient.type === 'radial') {\n\t\treturn {\n\t\t\tid,\n\t\t\ttype: 'radial',\n\t\t\tattrs: {\n\t\t\t\tcx: gradient.cx !== undefined ? `${gradient.cx * 100}%` : '50%',\n\t\t\t\tcy: gradient.cy !== undefined ? `${gradient.cy * 100}%` : '50%',\n\t\t\t\tr: gradient.r !== undefined ? `${gradient.r * 100}%` : '50%'\n\t\t\t},\n\t\t\tstops\n\t\t};\n\t}\n\n\tconst coords = angleToSvgLinearCoords(gradient.angle ?? 180);\n\treturn {\n\t\tid,\n\t\ttype: 'linear',\n\t\tattrs: {\n\t\t\tx1: `${coords.x1 * 100}%`,\n\t\t\ty1: `${coords.y1 * 100}%`,\n\t\t\tx2: `${coords.x2 * 100}%`,\n\t\t\ty2: `${coords.y2 * 100}%`\n\t\t},\n\t\tstops\n\t};\n}\n","/**\n * Shape geometry: shape descriptions (rect, rounded rect, ellipse, regular polygon, star and\n * SVG path data) to polygons, SVG path parsing / flattening, and polygons back to path data.\n */\nimport type { Point } from './types.js';\nimport type { MultiPolygon, PolygonFillRule, PolygonRing } from './polygonBoolean.js';\nimport { fitPathData } from './curveFit.js';\n\n// ---------------------------------------------------------------------------\n// Shape descriptions\n// ---------------------------------------------------------------------------\n\nimport type { CanvasGradientDef, SvgGradientDef } from './gradient.js';\nimport { gradientToSvgDef, resolveCanvasGradient } from './gradient.js';\n\n/** Paint shared by every shape kind. Colours are any CSS colour, preset name, or gradient definition. */\nexport interface ShapePaint {\n\tfill?: string | CanvasGradientDef;\n\tstroke?: string | CanvasGradientDef;\n\tstrokeWidth?: number;\n\topacity?: number;\n\tstrokeLinecap?: 'round' | 'butt' | 'square';\n\tstrokeLinejoin?: 'round' | 'bevel' | 'miter';\n\tfillGradient?: CanvasGradientDef;\n\tstrokeGradient?: CanvasGradientDef;\n}\n\ninterface ShapeBase extends ShapePaint {\n\t/** Clockwise rotation in degrees (on a y-down canvas) about the centre of the node box. */\n\trotation?: number;\n}\n\n/** Rectangle filling the node box, optionally with rounded corners. */\nexport interface RectShape extends ShapeBase {\n\tkind: 'rect';\n\t/** One radius, or `[topLeft, topRight, bottomRight, bottomLeft]`. Clamped to fit. */\n\tcornerRadius?: number | readonly [number, number, number, number];\n}\n\n/** Ellipse (circle when the node is square) inscribed in the node box. */\nexport interface EllipseShape extends ShapeBase {\n\tkind: 'ellipse';\n}\n\n/** Regular polygon, stretched so its bounding box fills the node box. */\nexport interface RegularPolygonShape extends ShapeBase {\n\tkind: 'polygon';\n\t/** Number of sides (at least 3, default 6). The first vertex points up. */\n\tsides?: number;\n}\n\n/** Star, stretched so its bounding box fills the node box. */\nexport interface StarShape extends ShapeBase {\n\tkind: 'star';\n\t/** Number of points (at least 3, default 5). The first point points up. */\n\tpoints?: number;\n\t/** Inner radius as a fraction of the outer radius (default 0.5). */\n\tinnerRadius?: number;\n}\n\n/** Arbitrary SVG path data. */\nexport interface PathShape extends ShapeBase {\n\tkind: 'path';\n\td: string;\n\t/**\n\t * `[minX, minY, width, height]` of the path's coordinate space, scaled to the node box.\n\t * Without it the path coordinates are node-local pixels.\n\t */\n\tviewBox?: readonly [number, number, number, number];\n\t/** Fill rule (default `nonzero`, as in SVG). */\n\tfillRule?: PolygonFillRule;\n}\n\n/**\n * Shape description stored on a node as `data.shape`. Geometry is relative to the node\n * box (`0,0` to `width,height`), so resizing the node resizes the shape.\n */\nexport type ShapeDescription = RectShape | EllipseShape | RegularPolygonShape | StarShape | PathShape;\n\nexport type ShapeKind = ShapeDescription['kind'];\n\nexport const SHAPE_KINDS: readonly ShapeKind[] = ['rect', 'ellipse', 'polygon', 'star', 'path'];\n\n/** Whether a value is a supported shape description. */\nexport function isShapeDescription(value: unknown): value is ShapeDescription {\n\tif (!value || typeof value !== 'object') return false;\n\tconst kind = (value as { kind?: unknown }).kind;\n\tif (!SHAPE_KINDS.includes(kind as ShapeKind)) return false;\n\treturn kind !== 'path' || typeof (value as PathShape).d === 'string';\n}\n\nexport interface ShapeSize {\n\twidth: number;\n\theight: number;\n}\n\nexport interface FlattenOptions {\n\t/** Maximum distance between a curve and its polyline, in output units (default 0.25). */\n\ttolerance?: number;\n\t/** Upper bound on the segments one curve is split into (default 512). */\n\tmaxSegments?: number;\n}\n\n// ---------------------------------------------------------------------------\n// SVG path parsing\n// ---------------------------------------------------------------------------\n\n/** Normalised absolute path command produced by `parseSvgPath`. */\nexport type PathCommand =\n\t| { type: 'M'; x: number; y: number }\n\t| { type: 'L'; x: number; y: number }\n\t| { type: 'C'; x1: number; y1: number; x2: number; y2: number; x: number; y: number }\n\t| { type: 'Q'; x1: number; y1: number; x: number; y: number }\n\t| {\n\t\t\ttype: 'A';\n\t\t\trx: number;\n\t\t\try: number;\n\t\t\trotation: number;\n\t\t\tlargeArc: boolean;\n\t\t\tsweep: boolean;\n\t\t\tx: number;\n\t\t\ty: number;\n\t }\n\t| { type: 'Z' };\n\nconst PARAM_COUNTS: Record<string, number> = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7, z: 0 };\n\n/** Character scanner for path data (handles `1.5.5`, `1e-3`, `-1-2` and compact arc flags). */\nclass PathScanner {\n\tprivate pos = 0;\n\tconstructor(private readonly text: string) {}\n\n\tprivate skip() {\n\t\twhile (this.pos < this.text.length && /[\\s,]/.test(this.text[this.pos])) this.pos++;\n\t}\n\n\t/** The next command letter, or `null` when a number (or the end) follows. */\n\tcommand(): string | null {\n\t\tthis.skip();\n\t\tconst ch = this.text[this.pos];\n\t\tif (ch && /[MmLlHhVvCcSsQqTtAaZz]/.test(ch)) {\n\t\t\tthis.pos++;\n\t\t\treturn ch;\n\t\t}\n\t\treturn null;\n\t}\n\n\tdone(): boolean {\n\t\tthis.skip();\n\t\treturn this.pos >= this.text.length;\n\t}\n\n\tnumber(): number | null {\n\t\tthis.skip();\n\t\tconst match = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?/.exec(this.text.slice(this.pos, this.pos + 64));\n\t\tif (!match) return null;\n\t\tthis.pos += match[0].length;\n\t\tconst value = Number(match[0]);\n\t\treturn Number.isFinite(value) ? value : null;\n\t}\n\n\tflag(): number | null {\n\t\tthis.skip();\n\t\tconst ch = this.text[this.pos];\n\t\tif (ch === '0' || ch === '1') {\n\t\t\tthis.pos++;\n\t\t\treturn ch === '1' ? 1 : 0;\n\t\t}\n\t\treturn null;\n\t}\n}\n\n/**\n * Parses SVG path data into absolute `M`, `L`, `C`, `Q`, `A` and `Z` commands. Relative\n * commands, `H` / `V`, smooth curves (`S` / `T`) and implicit repeated commands are\n * normalised. Parsing stops at the first malformed command (as browsers do).\n */\nexport function parseSvgPath(d: string): PathCommand[] {\n\tconst scanner = new PathScanner(d ?? '');\n\tconst commands: PathCommand[] = [];\n\tlet cx = 0;\n\tlet cy = 0;\n\tlet startX = 0;\n\tlet startY = 0;\n\tlet lastControl: { x: number; y: number; type: 'C' | 'Q' } | null = null;\n\tlet command = '';\n\n\twhile (!scanner.done()) {\n\t\tconst letter = scanner.command();\n\t\tif (letter) {\n\t\t\tcommand = letter;\n\t\t\tif (command === 'z' || command === 'Z') {\n\t\t\t\tcommands.push({ type: 'Z' });\n\t\t\t\tcx = startX;\n\t\t\t\tcy = startY;\n\t\t\t\tlastControl = null;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else if (!command || command === 'z' || command === 'Z') {\n\t\t\tbreak;\n\t\t}\n\t\tconst lower = command.toLowerCase();\n\t\tconst count = PARAM_COUNTS[lower];\n\t\tconst args: number[] = [];\n\t\tfor (let k = 0; k < count; k++) {\n\t\t\tconst value = lower === 'a' && (k === 3 || k === 4) ? scanner.flag() : scanner.number();\n\t\t\tif (value === null) return commands;\n\t\t\targs.push(value);\n\t\t}\n\t\tconst rel = command !== command.toUpperCase();\n\t\tconst ox = rel ? cx : 0;\n\t\tconst oy = rel ? cy : 0;\n\t\tswitch (lower) {\n\t\t\tcase 'm': {\n\t\t\t\tcx = args[0] + ox;\n\t\t\t\tcy = args[1] + oy;\n\t\t\t\tstartX = cx;\n\t\t\t\tstartY = cy;\n\t\t\t\tcommands.push({ type: 'M', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\t// Extra coordinate pairs after a moveto are implicit linetos.\n\t\t\t\tcommand = rel ? 'l' : 'L';\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'l':\n\t\t\t\tcx = args[0] + ox;\n\t\t\t\tcy = args[1] + oy;\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\tcx = args[0] + (rel ? cx : 0);\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'v':\n\t\t\t\tcy = args[0] + (rel ? cy : 0);\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'c': {\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'C',\n\t\t\t\t\tx1: args[0] + ox,\n\t\t\t\t\ty1: args[1] + oy,\n\t\t\t\t\tx2: args[2] + ox,\n\t\t\t\t\ty2: args[3] + oy,\n\t\t\t\t\tx: args[4] + ox,\n\t\t\t\t\ty: args[5] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x2, y: c.y2, type: 'C' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 's': {\n\t\t\t\tconst x1: number = lastControl?.type === 'C' ? 2 * cx - lastControl.x : cx;\n\t\t\t\tconst y1: number = lastControl?.type === 'C' ? 2 * cy - lastControl.y : cy;\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'C',\n\t\t\t\t\tx1,\n\t\t\t\t\ty1,\n\t\t\t\t\tx2: args[0] + ox,\n\t\t\t\t\ty2: args[1] + oy,\n\t\t\t\t\tx: args[2] + ox,\n\t\t\t\t\ty: args[3] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x2, y: c.y2, type: 'C' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'q': {\n\t\t\t\tconst c: PathCommand = { type: 'Q', x1: args[0] + ox, y1: args[1] + oy, x: args[2] + ox, y: args[3] + oy };\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x1, y: c.y1, type: 'Q' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 't': {\n\t\t\t\tconst x1: number = lastControl?.type === 'Q' ? 2 * cx - lastControl.x : cx;\n\t\t\t\tconst y1: number = lastControl?.type === 'Q' ? 2 * cy - lastControl.y : cy;\n\t\t\t\tconst c: PathCommand = { type: 'Q', x1, y1, x: args[0] + ox, y: args[1] + oy };\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: x1, y: y1, type: 'Q' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'a': {\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'A',\n\t\t\t\t\trx: Math.abs(args[0]),\n\t\t\t\t\try: Math.abs(args[1]),\n\t\t\t\t\trotation: args[2],\n\t\t\t\t\tlargeArc: args[3] !== 0,\n\t\t\t\t\tsweep: args[4] !== 0,\n\t\t\t\t\tx: args[5] + ox,\n\t\t\t\t\ty: args[6] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = null;\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn commands;\n}\n\n// ---------------------------------------------------------------------------\n// Curve flattening\n// ---------------------------------------------------------------------------\n\nfunction segmentCount(curvature: number, tolerance: number, max: number): number {\n\tif (!(curvature > 0)) return 1;\n\treturn Math.max(1, Math.min(max, Math.ceil(Math.sqrt(curvature / tolerance))));\n}\n\nfunction flattenCubic(\n\tout: Point[],\n\tp0: Point,\n\tx1: number,\n\ty1: number,\n\tx2: number,\n\ty2: number,\n\tx: number,\n\ty: number,\n\ttolerance: number,\n\tmax: number\n) {\n\t// Uniform subdivision error <= max|B''| / (8 n²) with |B''| <= 6 * max second difference.\n\tconst dd = Math.max(\n\t\tMath.hypot(p0[0] - 2 * x1 + x2, p0[1] - 2 * y1 + y2),\n\t\tMath.hypot(x1 - 2 * x2 + x, y1 - 2 * y2 + y)\n\t);\n\tconst n = segmentCount((6 * dd) / 8, tolerance, max);\n\tfor (let k = 1; k <= n; k++) {\n\t\tconst t = k / n;\n\t\tconst mt = 1 - t;\n\t\tconst a = mt * mt * mt;\n\t\tconst b = 3 * mt * mt * t;\n\t\tconst c = 3 * mt * t * t;\n\t\tconst e = t * t * t;\n\t\tout.push([a * p0[0] + b * x1 + c * x2 + e * x, a * p0[1] + b * y1 + c * y2 + e * y]);\n\t}\n}\n\nfunction flattenQuadratic(\n\tout: Point[],\n\tp0: Point,\n\tx1: number,\n\ty1: number,\n\tx: number,\n\ty: number,\n\ttolerance: number,\n\tmax: number\n) {\n\tconst dd = Math.hypot(p0[0] - 2 * x1 + x, p0[1] - 2 * y1 + y);\n\tconst n = segmentCount((2 * dd) / 8, tolerance, max);\n\tfor (let k = 1; k <= n; k++) {\n\t\tconst t = k / n;\n\t\tconst mt = 1 - t;\n\t\tout.push([mt * mt * p0[0] + 2 * mt * t * x1 + t * t * x, mt * mt * p0[1] + 2 * mt * t * y1 + t * t * y]);\n\t}\n}\n\nfunction arcSteps(radius: number, sweep: number, tolerance: number, max: number): number {\n\tif (radius <= tolerance) return Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2)));\n\tconst step = 2 * Math.acos(1 - tolerance / radius);\n\treturn Math.max(1, Math.min(max, Math.ceil(Math.abs(sweep) / step)));\n}\n\n/** Flattens an SVG elliptical arc (endpoint parameterisation, SVG 1.1 F.6.5). */\nfunction flattenArc(\n\tout: Point[],\n\tp0: Point,\n\tcmd: Extract<PathCommand, { type: 'A' }>,\n\ttolerance: number,\n\tmax: number\n) {\n\tconst [x0, y0] = p0;\n\tlet { rx, ry } = cmd;\n\tconst { x, y } = cmd;\n\tif (x0 === x && y0 === y) return;\n\tif (rx === 0 || ry === 0) {\n\t\tout.push([x, y]);\n\t\treturn;\n\t}\n\tconst phi = (cmd.rotation * Math.PI) / 180;\n\tconst cos = Math.cos(phi);\n\tconst sin = Math.sin(phi);\n\tconst dx = (x0 - x) / 2;\n\tconst dy = (y0 - y) / 2;\n\tconst x1p = cos * dx + sin * dy;\n\tconst y1p = -sin * dx + cos * dy;\n\tconst lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);\n\tif (lambda > 1) {\n\t\tconst s = Math.sqrt(lambda);\n\t\trx *= s;\n\t\try *= s;\n\t}\n\tconst num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;\n\tconst den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;\n\tlet coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));\n\tif (cmd.largeArc === cmd.sweep) coef = -coef;\n\tconst cxp = (coef * rx * y1p) / ry;\n\tconst cyp = (-coef * ry * x1p) / rx;\n\tconst cx = cos * cxp - sin * cyp + (x0 + x) / 2;\n\tconst cy = sin * cxp + cos * cyp + (y0 + y) / 2;\n\tconst angle = (ux: number, uy: number, vx: number, vy: number) => {\n\t\tconst a = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);\n\t\treturn a;\n\t};\n\tconst theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);\n\tlet delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);\n\tif (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;\n\telse if (cmd.sweep && delta < 0) delta += 2 * Math.PI;\n\tconst n = arcSteps(Math.max(rx, ry), delta, tolerance, max);\n\tfor (let k = 1; k < n; k++) {\n\t\tconst t = theta1 + (delta * k) / n;\n\t\tconst ex = rx * Math.cos(t);\n\t\tconst ey = ry * Math.sin(t);\n\t\tout.push([cos * ex - sin * ey + cx, sin * ex + cos * ey + cy]);\n\t}\n\tout.push([x, y]);\n}\n\n/**\n * Flattens SVG path data into closed rings (one per subpath; open subpaths are closed the\n * way SVG fills them). Curves are approximated within `tolerance`.\n */\nexport function flattenSvgPath(d: string | PathCommand[], options: FlattenOptions = {}): PolygonRing[] {\n\tconst tolerance = options.tolerance && options.tolerance > 0 ? options.tolerance : 0.25;\n\tconst max = options.maxSegments ?? 512;\n\tconst commands = typeof d === 'string' ? parseSvgPath(d) : d;\n\tconst rings: PolygonRing[] = [];\n\tlet current: Point[] = [];\n\tlet start: Point = [0, 0];\n\tconst flush = () => {\n\t\tif (current.length >= 3) rings.push(current);\n\t\tcurrent = [];\n\t};\n\tconst last = (): Point => current[current.length - 1] ?? start;\n\tfor (const cmd of commands) {\n\t\tswitch (cmd.type) {\n\t\t\tcase 'M':\n\t\t\t\tflush();\n\t\t\t\tstart = [cmd.x, cmd.y];\n\t\t\t\tcurrent = [start];\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tcurrent.push([cmd.x, cmd.y]);\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenCubic(current, last(), cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'Q':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenQuadratic(current, last(), cmd.x1, cmd.y1, cmd.x, cmd.y, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'A':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenArc(current, last(), cmd, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'Z':\n\t\t\t\tflush();\n\t\t\t\t// A command after Z without a moveto starts at the subpath start.\n\t\t\t\tbreak;\n\t\t}\n\t}\n\tflush();\n\treturn rings.map(dedupeRing).filter((r) => r.length >= 3);\n}\n\nfunction dedupeRing(ring: Point[]): Point[] {\n\tconst out: Point[] = [];\n\tfor (const p of ring) {\n\t\tconst prev = out[out.length - 1];\n\t\tif (!prev || prev[0] !== p[0] || prev[1] !== p[1]) out.push(p);\n\t}\n\twhile (out.length > 1 && out[0][0] === out[out.length - 1][0] && out[0][1] === out[out.length - 1][1]) out.pop();\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Shapes to polygons\n// ---------------------------------------------------------------------------\n\nfunction cornerRadii(shape: RectShape, width: number, height: number): [number, number, number, number] {\n\tconst raw = shape.cornerRadius ?? 0;\n\tlet radii = (typeof raw === 'number' ? [raw, raw, raw, raw] : [...raw]).map((r) =>\n\t\tNumber.isFinite(r) && r > 0 ? r : 0\n\t) as [number, number, number, number];\n\t// Scale all radii down together when adjacent corners would overlap (CSS rule).\n\tconst factor = Math.min(\n\t\t1,\n\t\twidth / Math.max(radii[0] + radii[1], 1e-12),\n\t\theight / Math.max(radii[1] + radii[2], 1e-12),\n\t\twidth / Math.max(radii[2] + radii[3], 1e-12),\n\t\theight / Math.max(radii[3] + radii[0], 1e-12)\n\t);\n\tif (factor < 1) radii = radii.map((r) => r * factor) as [number, number, number, number];\n\treturn radii;\n}\n\n/** Unit vertices of a regular polygon / star, stretched to fill the box. */\nfunction polygonVertices(count: number, inner: number | null, width: number, height: number): Point[] {\n\tconst raw: Point[] = [];\n\tconst total = inner === null ? count : count * 2;\n\tfor (let k = 0; k < total; k++) {\n\t\tconst angle = -Math.PI / 2 + (k * 2 * Math.PI) / total;\n\t\tconst r = inner !== null && k % 2 === 1 ? inner : 1;\n\t\traw.push([r * Math.cos(angle), r * Math.sin(angle)]);\n\t}\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const [x, y] of raw) {\n\t\tminX = Math.min(minX, x);\n\t\tminY = Math.min(minY, y);\n\t\tmaxX = Math.max(maxX, x);\n\t\tmaxY = Math.max(maxY, y);\n\t}\n\tconst sx = width / (maxX - minX || 1);\n\tconst sy = height / (maxY - minY || 1);\n\treturn raw.map(([x, y]) => [(x - minX) * sx, (y - minY) * sy]);\n}\n\nfunction rotationTransform(shape: ShapeDescription, size: ShapeSize): ((p: Point) => Point) | null {\n\tconst deg = shape.rotation ?? 0;\n\tif (!deg || !Number.isFinite(deg)) return null;\n\tconst rad = (deg * Math.PI) / 180;\n\tconst cos = Math.cos(rad);\n\tconst sin = Math.sin(rad);\n\tconst cx = size.width / 2;\n\tconst cy = size.height / 2;\n\treturn ([x, y]) => [cx + (x - cx) * cos - (y - cy) * sin, cy + (x - cx) * sin + (y - cy) * cos];\n}\n\n/**\n * SVG path data for a shape in node-local pixels (exact curves: arcs for rounded corners\n * and ellipses), plus the fill rule and a `transform` for rotation / path view boxes.\n * Use it to render a shape; use `shapeToPolygon` for geometry.\n */\nexport function getShapeSvg(\n\tshape: ShapeDescription,\n\tsize: ShapeSize\n): { d: string; transform?: string; fillRule: PolygonFillRule } {\n\tconst { width: w, height: h } = size;\n\tconst f = (n: number) => formatNumber(n, 3);\n\tconst transforms: string[] = [];\n\tif (shape.rotation) transforms.push(`rotate(${f(shape.rotation)} ${f(w / 2)} ${f(h / 2)})`);\n\tlet d = '';\n\tlet fillRule: PolygonFillRule = 'nonzero';\n\tswitch (shape.kind) {\n\t\tcase 'rect': {\n\t\t\tconst [tl, tr, br, bl] = cornerRadii(shape, w, h);\n\t\t\tif (tl + tr + br + bl === 0) {\n\t\t\t\td = `M0 0H${f(w)}V${f(h)}H0Z`;\n\t\t\t} else {\n\t\t\t\tconst arc = (r: number, x: number, y: number) => (r > 0 ? `A${f(r)} ${f(r)} 0 0 1 ${f(x)} ${f(y)}` : '');\n\t\t\t\td =\n\t\t\t\t\t`M${f(tl)} 0H${f(w - tr)}${arc(tr, w, tr)}V${f(h - br)}${arc(br, w - br, h)}` +\n\t\t\t\t\t`H${f(bl)}${arc(bl, 0, h - bl)}V${f(tl)}${arc(tl, tl, 0)}Z`;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'ellipse': {\n\t\t\tconst rx = w / 2;\n\t\t\tconst ry = h / 2;\n\t\t\td = `M0 ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 ${f(w)} ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 0 ${f(ry)}Z`;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'polygon':\n\t\tcase 'star': {\n\t\t\tconst points =\n\t\t\t\tshape.kind === 'polygon'\n\t\t\t\t\t? polygonVertices(Math.max(3, Math.floor(shape.sides ?? 6)), null, w, h)\n\t\t\t\t\t: polygonVertices(Math.max(3, Math.floor(shape.points ?? 5)), clampRatio(shape.innerRadius), w, h);\n\t\t\td = ringsToPath([points], f);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'path': {\n\t\t\td = shape.d;\n\t\t\tfillRule = shape.fillRule ?? 'nonzero';\n\t\t\tconst vb = validViewBox(shape.viewBox);\n\t\t\tif (vb) {\n\t\t\t\ttransforms.push(\n\t\t\t\t\t`scale(${f(w / vb[2])} ${f(h / vb[3])})`,\n\t\t\t\t\t`translate(${f(-vb[0])} ${f(-vb[1])})`\n\t\t\t\t);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn transforms.length > 0 ? { d, transform: transforms.join(' '), fillRule } : { d, fillRule };\n}\n\n/** Everything a renderer needs to draw a shape as one SVG `<path>`. */\nexport interface ShapeRenderData {\n\t/** SVG `viewBox` (`0 0 width height`): node-local pixels. */\n\tviewBox: string;\n\twidth: number;\n\theight: number;\n\td: string;\n\ttransform?: string;\n\tfillRule: PolygonFillRule;\n\tfill: string;\n\tstroke: string;\n\tstrokeWidth: number;\n\topacity?: number;\n\tstrokeLinecap?: 'round' | 'butt' | 'square';\n\tstrokeLinejoin?: 'round' | 'bevel' | 'miter';\n\t/** Gradient def for the fill, when fill uses a gradient. */\n\tfillGradient?: SvgGradientDef;\n\t/** Gradient def for the stroke, when stroke uses a gradient. */\n\tstrokeGradient?: SvgGradientDef;\n}\n\nexport interface GetShapeRenderDataOptions {\n\t/** Unique ID suffix or base ID used when generating SVG gradient IDs (default: auto random). */\n\tid?: string;\n}\n\n/**\n * Resolves the SVG drawing of a shape for a node size. Without a size, a path's view box\n * size (or 100x100) is used. Paint defaults: `fill: currentColor`, `stroke: none`,\n * `strokeWidth: 1`. Supports solid colors, CSS gradients, presets, and CanvasGradientDef.\n */\nexport function getShapeRenderData(\n\tshape: ShapeDescription,\n\tsize: Partial<ShapeSize> = {},\n\toptions: GetShapeRenderDataOptions = {}\n): ShapeRenderData {\n\tconst vb = shape.kind === 'path' ? validViewBox(shape.viewBox) : null;\n\tconst width = size.width && size.width > 0 ? size.width : vb ? vb[2] : 100;\n\tconst height = size.height && size.height > 0 ? size.height : vb ? vb[3] : 100;\n\tconst svg = getShapeSvg(shape, { width, height });\n\n\tconst baseId = options.id || Math.random().toString(36).slice(2, 9);\n\n\t// Resolve fill (gradient or solid)\n\tlet fillStr = 'currentColor';\n\tlet fillGradientDef: SvgGradientDef | undefined;\n\tconst rawFill = shape.fillGradient ?? shape.fill;\n\tconst resolvedFillGradient = resolveCanvasGradient(rawFill);\n\tif (resolvedFillGradient) {\n\t\tconst gradId = `shape-fill-${baseId}`;\n\t\tfillGradientDef = gradientToSvgDef(resolvedFillGradient, gradId);\n\t\tfillStr = `url(#${gradId})`;\n\t} else if (typeof shape.fill === 'string') {\n\t\tfillStr = shape.fill;\n\t} else if (shape.fill === undefined) {\n\t\tfillStr = 'currentColor';\n\t}\n\n\t// Resolve stroke (gradient or solid)\n\tlet strokeStr = 'none';\n\tlet strokeGradientDef: SvgGradientDef | undefined;\n\tconst rawStroke = shape.strokeGradient ?? shape.stroke;\n\tconst resolvedStrokeGradient = resolveCanvasGradient(rawStroke);\n\tif (resolvedStrokeGradient) {\n\t\tconst gradId = `shape-stroke-${baseId}`;\n\t\tstrokeGradientDef = gradientToSvgDef(resolvedStrokeGradient, gradId);\n\t\tstrokeStr = `url(#${gradId})`;\n\t} else if (typeof shape.stroke === 'string') {\n\t\tstrokeStr = shape.stroke;\n\t} else if (shape.stroke === undefined) {\n\t\tstrokeStr = 'none';\n\t}\n\n\tconst data: ShapeRenderData = {\n\t\tviewBox: `0 0 ${formatNumber(width, 3)} ${formatNumber(height, 3)}`,\n\t\twidth,\n\t\theight,\n\t\td: svg.d,\n\t\tfillRule: svg.fillRule,\n\t\tfill: fillStr,\n\t\tstroke: strokeStr,\n\t\tstrokeWidth: shape.strokeWidth ?? 1\n\t};\n\tif (svg.transform) data.transform = svg.transform;\n\tif (shape.opacity !== undefined) data.opacity = shape.opacity;\n\tif (shape.strokeLinecap) data.strokeLinecap = shape.strokeLinecap;\n\tif (shape.strokeLinejoin) data.strokeLinejoin = shape.strokeLinejoin;\n\tif (fillGradientDef) data.fillGradient = fillGradientDef;\n\tif (strokeGradientDef) data.strokeGradient = strokeGradientDef;\n\treturn data;\n}\n\nfunction clampRatio(value: number | undefined): number {\n\tif (value === undefined || !Number.isFinite(value)) return 0.5;\n\treturn Math.min(1, Math.max(0.01, value));\n}\n\nfunction validViewBox(vb: PathShape['viewBox']): readonly [number, number, number, number] | null {\n\tif (!vb || vb.length !== 4 || !vb.every(Number.isFinite) || vb[2] <= 0 || vb[3] <= 0) return null;\n\treturn vb;\n}\n\n/**\n * Converts a shape to rings in node-local pixels (rotation applied), flattening curves at\n * `tolerance`. Returns the rings with the fill rule they must be read with.\n */\nexport function shapeToPolygon(\n\tshape: ShapeDescription,\n\tsize: ShapeSize,\n\toptions: FlattenOptions = {}\n): { rings: PolygonRing[]; fillRule: PolygonFillRule } {\n\tconst { width: w, height: h } = size;\n\tif (!(w > 0) || !(h > 0)) return { rings: [], fillRule: 'nonzero' };\n\tlet rings: PolygonRing[];\n\tlet fillRule: PolygonFillRule = 'nonzero';\n\tif (shape.kind === 'path') {\n\t\tfillRule = shape.fillRule ?? 'nonzero';\n\t\tconst vb = validViewBox(shape.viewBox);\n\t\tif (vb) {\n\t\t\tconst sx = w / vb[2];\n\t\t\tconst sy = h / vb[3];\n\t\t\t// Flatten in path units with the tolerance scaled to stay within `tolerance` px.\n\t\t\tconst tolerance = (options.tolerance ?? 0.25) / Math.max(sx, sy);\n\t\t\trings = flattenSvgPath(shape.d, { ...options, tolerance }).map((ring) =>\n\t\t\t\tring.map(([x, y]) => [(x - vb[0]) * sx, (y - vb[1]) * sy] as Point)\n\t\t\t);\n\t\t} else {\n\t\t\trings = flattenSvgPath(shape.d, options);\n\t\t}\n\t} else {\n\t\tconst { d } = getShapeSvg({ ...shape, rotation: 0 }, size);\n\t\trings = flattenSvgPath(d, options);\n\t}\n\tconst rotate = rotationTransform(shape, size);\n\tif (rotate) rings = rings.map((ring) => ring.map(rotate));\n\treturn { rings, fillRule };\n}\n\n// ---------------------------------------------------------------------------\n// Polygons to path data\n// ---------------------------------------------------------------------------\n\n/** Formats a number with at most `precision` decimals, without trailing zeros. */\nexport function formatNumber(value: number, precision = 3): string {\n\tconst factor = 10 ** precision;\n\tconst rounded = Math.round(value * factor) / factor;\n\treturn Object.is(rounded, -0) ? '0' : String(rounded);\n}\n\nfunction ringsToPath(rings: readonly (readonly Point[])[], f: (n: number) => string): string {\n\tlet d = '';\n\tfor (const ring of rings) {\n\t\tif (ring.length < 2) continue;\n\t\td += `M${f(ring[0][0])} ${f(ring[0][1])}`;\n\t\tfor (let i = 1; i < ring.length; i++) d += `L${f(ring[i][0])} ${f(ring[i][1])}`;\n\t\td += 'Z';\n\t}\n\treturn d;\n}\n\n/** Perpendicular distance from `p` to the segment `a`–`b`. */\nfunction segmentDistance(p: Point, a: Point, b: Point): number {\n\tconst dx = b[0] - a[0];\n\tconst dy = b[1] - a[1];\n\tconst len2 = dx * dx + dy * dy;\n\tif (len2 === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);\n\tconst t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));\n\treturn Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy));\n}\n\nfunction rdp(points: Point[], tolerance: number): Point[] {\n\tif (points.length <= 2) return points;\n\tconst keep = new Uint8Array(points.length);\n\tkeep[0] = 1;\n\tkeep[points.length - 1] = 1;\n\tconst stack: [number, number][] = [[0, points.length - 1]];\n\twhile (stack.length) {\n\t\tconst [s, e] = stack.pop()!;\n\t\tlet maxDist = -1;\n\t\tlet index = -1;\n\t\tfor (let i = s + 1; i < e; i++) {\n\t\t\tconst dist = segmentDistance(points[i], points[s], points[e]);\n\t\t\tif (dist > maxDist) {\n\t\t\t\tmaxDist = dist;\n\t\t\t\tindex = i;\n\t\t\t}\n\t\t}\n\t\tif (index !== -1 && maxDist > tolerance) {\n\t\t\tkeep[index] = 1;\n\t\t\tstack.push([s, index], [index, e]);\n\t\t}\n\t}\n\treturn points.filter((_, i) => keep[i]);\n}\n\n/**\n * Simplifies a closed ring with Ramer–Douglas–Peucker at `tolerance`. Returns an empty\n * ring when fewer than three points survive.\n */\nexport function simplifyRing(ring: readonly Point[], tolerance: number): Point[] {\n\tif (ring.length <= 3 || !(tolerance > 0)) return [...ring];\n\t// Anchor on the point farthest from the first one so the closing seam is not lost.\n\tlet far = 0;\n\tlet farDist = -1;\n\tfor (let i = 1; i < ring.length; i++) {\n\t\tconst dist = Math.hypot(ring[i][0] - ring[0][0], ring[i][1] - ring[0][1]);\n\t\tif (dist > farDist) {\n\t\t\tfarDist = dist;\n\t\t\tfar = i;\n\t\t}\n\t}\n\tconst first = rdp(ring.slice(0, far + 1) as Point[], tolerance);\n\tconst second = rdp([...ring.slice(far), ring[0]] as Point[], tolerance);\n\tconst out = [...first.slice(0, -1), ...second.slice(0, -1)];\n\treturn out.length >= 3 ? out : [];\n}\n\nexport interface PolygonToPathOptions {\n\t/** Decimal places in the output (default 3). */\n\tprecision?: number;\n\t/** Ramer–Douglas–Peucker tolerance; `0` (default) keeps every vertex. */\n\tsimplify?: number;\n\t/** Added to every point before formatting (e.g. to make coordinates node-local). */\n\toffset?: { x: number; y: number };\n\t/**\n\t * `'lines'` (default) writes every ring as `M`/`L`/`Z`. `'fit'` fits cubic Béziers to each\n\t * ring (`fitPathData`), so flattened curves come back as smooth `C` curves while sharp\n\t * corners (turning more than `cornerAngleDeg`) and straight edges stay as they are.\n\t */\n\tcurves?: 'lines' | 'fit';\n\t/** Curve-fitting tolerance in path units for `curves: 'fit'` (default 0.25). */\n\tcurveTolerance?: number;\n\t/** Corner threshold in degrees for `curves: 'fit'` (default 30). */\n\tcornerAngleDeg?: number;\n}\n\n/**\n * Converts a multi-polygon (or a list of rings) to SVG path data using `M`/`L`/`Z`. Holes\n * keep their opposite orientation, so the path fills correctly with either fill rule.\n */\nexport function polygonToSvgPath(input: MultiPolygon | PolygonRing[], options: PolygonToPathOptions = {}): string {\n\tconst precision = options.precision ?? 3;\n\tconst f = (n: number) => formatNumber(n, precision);\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tconst rings: Point[][] = [];\n\tconst fit = options.curves === 'fit';\n\tlet fitted = '';\n\tfor (const item of input as any[]) {\n\t\tconst list: Point[][] =\n\t\t\titem.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0]) ? item : [item];\n\t\tfor (const ring of list) {\n\t\t\tlet r = ring;\n\t\t\tif (options.simplify && options.simplify > 0) r = simplifyRing(r, options.simplify);\n\t\t\tif (r.length < 3) continue;\n\t\t\tif (fit) {\n\t\t\t\tfitted += fitPathData(r, {\n\t\t\t\t\ttolerance: options.curveTolerance ?? 0.25,\n\t\t\t\t\tcornerAngleDeg: options.cornerAngleDeg,\n\t\t\t\t\tprecision,\n\t\t\t\t\toffset: { x: dx, y: dy }\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\trings.push(dx || dy ? r.map(([x, y]) => [x + dx, y + dy] as Point) : r);\n\t\t}\n\t}\n\treturn fit ? fitted : ringsToPath(rings, f);\n}\n","/**\n * Bézier path model shared by the curve-preserving path operations: absolute `M` / `L` /\n * `Q` / `C` / `Z` commands, conversion from SVG path data (arcs become cubics), segment\n * math (evaluation, splitting, extrema, nearest point), exact areas, winding numbers and\n * arc-length utilities.\n */\nimport type { Point } from '../types.js';\nimport { formatNumber, parseSvgPath, type PathCommand } from '../shapeGeometry.js';\nimport type { PolygonFillRule } from '../polygonBoolean.js';\n\n// ---------------------------------------------------------------------------\n// Public path model\n// ---------------------------------------------------------------------------\n\n/** One absolute path command. Every subpath starts with `M`; `Z` closes it. */\nexport type BezierCommand =\n\t| { type: 'M'; x: number; y: number }\n\t| { type: 'L'; x: number; y: number }\n\t| { type: 'Q'; x1: number; y1: number; x: number; y: number }\n\t| { type: 'C'; x1: number; y1: number; x2: number; y2: number; x: number; y: number }\n\t| { type: 'Z' };\n\n/** A path as a list of absolute move / line / quadratic / cubic / close commands. */\nexport type BezierPath = BezierCommand[];\n\n/** Anything the path operations accept as a path: SVG path data, parsed commands or a `BezierPath`. */\nexport type BezierPathInput = string | readonly BezierCommand[] | readonly PathCommand[];\n\n/**\n * One segment as its control points: 2 points for a line, 3 for a quadratic and 4 for a\n * cubic Bézier.\n */\nexport type BezierSegment = readonly Point[];\n\n/** A subpath as a chain of segments (each starts where the previous one ends). */\nexport interface BezierContour {\n\tsegments: BezierSegment[];\n\tclosed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Vector helpers\n// ---------------------------------------------------------------------------\n\nexport const sub = (a: Point, b: Point): Point => [a[0] - b[0], a[1] - b[1]];\nexport const add = (a: Point, b: Point): Point => [a[0] + b[0], a[1] + b[1]];\nexport const scale = (a: Point, s: number): Point => [a[0] * s, a[1] * s];\nexport const dot = (a: Point, b: Point) => a[0] * b[0] + a[1] * b[1];\nexport const cross = (a: Point, b: Point) => a[0] * b[1] - a[1] * b[0];\nexport const dist = (a: Point, b: Point) => Math.hypot(a[0] - b[0], a[1] - b[1]);\nexport const lerp = (a: Point, b: Point, t: number): Point => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];\nexport const samePoint = (a: Point, b: Point) => a[0] === b[0] && a[1] === b[1];\n\nexport function unit(v: Point): Point | null {\n\tconst l = Math.hypot(v[0], v[1]);\n\treturn l > 0 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;\n}\n\n// ---------------------------------------------------------------------------\n// Segment math\n// ---------------------------------------------------------------------------\n\n/** Point at parameter `t` (de Casteljau, works for any degree). */\nexport function segmentPoint(seg: BezierSegment, t: number): Point {\n\tconst n = seg.length;\n\tif (n === 2) return lerp(seg[0], seg[1], t);\n\tconst mt = 1 - t;\n\tif (n === 3) {\n\t\tconst a = mt * mt;\n\t\tconst b = 2 * mt * t;\n\t\tconst c = t * t;\n\t\treturn [a * seg[0][0] + b * seg[1][0] + c * seg[2][0], a * seg[0][1] + b * seg[1][1] + c * seg[2][1]];\n\t}\n\tconst a = mt * mt * mt;\n\tconst b = 3 * mt * mt * t;\n\tconst c = 3 * mt * t * t;\n\tconst d = t * t * t;\n\treturn [\n\t\ta * seg[0][0] + b * seg[1][0] + c * seg[2][0] + d * seg[3][0],\n\t\ta * seg[0][1] + b * seg[1][1] + c * seg[2][1] + d * seg[3][1]\n\t];\n}\n\n/** First derivative at `t`. */\nexport function segmentDerivative(seg: BezierSegment, t: number): Point {\n\tconst n = seg.length;\n\tif (n === 2) return sub(seg[1], seg[0]);\n\tif (n === 3) {\n\t\tconst a = sub(seg[1], seg[0]);\n\t\tconst b = sub(seg[2], seg[1]);\n\t\treturn [2 * ((1 - t) * a[0] + t * b[0]), 2 * ((1 - t) * a[1] + t * b[1])];\n\t}\n\tconst a = sub(seg[1], seg[0]);\n\tconst b = sub(seg[2], seg[1]);\n\tconst c = sub(seg[3], seg[2]);\n\tconst mt = 1 - t;\n\treturn [\n\t\t3 * (mt * mt * a[0] + 2 * mt * t * b[0] + t * t * c[0]),\n\t\t3 * (mt * mt * a[1] + 2 * mt * t * b[1] + t * t * c[1])\n\t];\n}\n\n/** Second derivative at `t`. */\nexport function segmentSecondDerivative(seg: BezierSegment, t: number): Point {\n\tconst n = seg.length;\n\tif (n === 2) return [0, 0];\n\tif (n === 3) return [2 * (seg[2][0] - 2 * seg[1][0] + seg[0][0]), 2 * (seg[2][1] - 2 * seg[1][1] + seg[0][1])];\n\tconst a: Point = [seg[2][0] - 2 * seg[1][0] + seg[0][0], seg[2][1] - 2 * seg[1][1] + seg[0][1]];\n\tconst b: Point = [seg[3][0] - 2 * seg[2][0] + seg[1][0], seg[3][1] - 2 * seg[2][1] + seg[1][1]];\n\treturn [6 * ((1 - t) * a[0] + t * b[0]), 6 * ((1 - t) * a[1] + t * b[1])];\n}\n\n/**\n * Unit tangent at `t`. Where the derivative vanishes (a handle on its anchor) the direction\n * a hair further along is used, and the chord as the last resort.\n */\nexport function segmentTangent(seg: BezierSegment, t: number): Point {\n\tconst d = unit(segmentDerivative(seg, t));\n\tif (d) return d;\n\tconst h = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);\n\tconst e = unit(segmentDerivative(seg, h));\n\tif (e) return e;\n\treturn unit(sub(seg[seg.length - 1], seg[0])) ?? [1, 0];\n}\n\n/** Signed curvature at `t` (positive when turning counter-clockwise in a y-up frame). */\nexport function segmentCurvature(seg: BezierSegment, t: number): number {\n\tif (seg.length === 2) return 0;\n\tlet d = segmentDerivative(seg, t);\n\tlet tt = t;\n\tif (Math.hypot(d[0], d[1]) < 1e-12) {\n\t\ttt = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);\n\t\td = segmentDerivative(seg, tt);\n\t}\n\tconst dd = segmentSecondDerivative(seg, tt);\n\tconst len = Math.hypot(d[0], d[1]);\n\tif (!(len > 0)) return 0;\n\treturn cross(d, dd) / (len * len * len);\n}\n\n/** Splits a segment at `t` (de Casteljau). */\nexport function splitSegment(seg: BezierSegment, t: number): [Point[], Point[]] {\n\tconst n = seg.length;\n\tif (n === 2) {\n\t\tconst m = lerp(seg[0], seg[1], t);\n\t\treturn [\n\t\t\t[seg[0], m],\n\t\t\t[m, seg[1]]\n\t\t];\n\t}\n\tif (n === 3) {\n\t\tconst a = lerp(seg[0], seg[1], t);\n\t\tconst b = lerp(seg[1], seg[2], t);\n\t\tconst m = lerp(a, b, t);\n\t\treturn [\n\t\t\t[seg[0], a, m],\n\t\t\t[m, b, seg[2]]\n\t\t];\n\t}\n\tconst a = lerp(seg[0], seg[1], t);\n\tconst b = lerp(seg[1], seg[2], t);\n\tconst c = lerp(seg[2], seg[3], t);\n\tconst ab = lerp(a, b, t);\n\tconst bc = lerp(b, c, t);\n\tconst m = lerp(ab, bc, t);\n\treturn [\n\t\t[seg[0], a, ab, m],\n\t\t[m, bc, c, seg[3]]\n\t];\n}\n\n/** The part of a segment between parameters `t0` and `t1` (reversed when `t1 < t0`). */\nexport function subSegment(seg: BezierSegment, t0: number, t1: number): Point[] {\n\tif (t1 < t0) return subSegment(seg, t1, t0).reverse();\n\tif (t0 <= 0 && t1 >= 1) return [...seg];\n\tlet s: Point[] = [...seg];\n\tif (t1 < 1) s = splitSegment(s, t1)[0];\n\tif (t0 > 0) s = splitSegment(s, t1 > 0 ? t0 / t1 : 0)[1];\n\treturn s;\n}\n\n/** Whether every control point lies within `tol` of the chord (a line in disguise). */\nexport function isStraightSegment(seg: BezierSegment, tol: number): boolean {\n\tif (seg.length === 2) return true;\n\tconst a = seg[0];\n\tconst b = seg[seg.length - 1];\n\tconst chord = sub(b, a);\n\tconst len = Math.hypot(chord[0], chord[1]);\n\tfor (let i = 1; i < seg.length - 1; i++) {\n\t\tconst v = sub(seg[i], a);\n\t\tif (len === 0) {\n\t\t\tif (Math.hypot(v[0], v[1]) > tol) return false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Math.abs(cross(chord, v)) / len > tol) return false;\n\t\tconst along = dot(chord, v) / len;\n\t\tif (along < -tol || along > len + tol) return false;\n\t}\n\treturn true;\n}\n\n/** Real roots in the open interval (0, 1) of `a t² + b t + c`. */\nfunction quadraticRoots(a: number, b: number, c: number, out: number[]) {\n\tconst push = (t: number) => {\n\t\tif (t > 1e-12 && t < 1 - 1e-12) out.push(t);\n\t};\n\tconst scaleAbs = Math.max(Math.abs(a), Math.abs(b), Math.abs(c));\n\tif (scaleAbs === 0) return;\n\tif (Math.abs(a) <= 1e-12 * scaleAbs) {\n\t\tif (b !== 0) push(-c / b);\n\t\treturn;\n\t}\n\tconst disc = b * b - 4 * a * c;\n\tif (disc < 0) return;\n\tconst sq = Math.sqrt(disc);\n\tconst q = -0.5 * (b + (b >= 0 ? sq : -sq));\n\tpush(q / a);\n\tif (q !== 0) push(c / q);\n}\n\n/** Parameters in (0, 1) where the segment's `axis` coordinate has a local extremum. */\nexport function segmentExtrema(seg: BezierSegment, axis: 0 | 1): number[] {\n\tconst out: number[] = [];\n\tif (seg.length === 3) {\n\t\tconst d = seg[0][axis] - 2 * seg[1][axis] + seg[2][axis];\n\t\tif (d !== 0) {\n\t\t\tconst t = (seg[0][axis] - seg[1][axis]) / d;\n\t\t\tif (t > 1e-12 && t < 1 - 1e-12) out.push(t);\n\t\t}\n\t} else if (seg.length === 4) {\n\t\tconst p0 = seg[0][axis];\n\t\tconst p1 = seg[1][axis];\n\t\tconst p2 = seg[2][axis];\n\t\tconst p3 = seg[3][axis];\n\t\t// B'(t)/3 = a t² + b t + c\n\t\tconst a = -p0 + 3 * p1 - 3 * p2 + p3;\n\t\tconst b = 2 * (p0 - 2 * p1 + p2);\n\t\tconst c = p1 - p0;\n\t\tquadraticRoots(a, b, c, out);\n\t}\n\treturn out.sort((x, y) => x - y);\n}\n\n/** Exact axis-aligned bounds of a segment. */\nexport function segmentBounds(seg: BezierSegment): { minX: number; minY: number; maxX: number; maxY: number } {\n\tconst a = seg[0];\n\tconst b = seg[seg.length - 1];\n\tlet minX = Math.min(a[0], b[0]);\n\tlet maxX = Math.max(a[0], b[0]);\n\tlet minY = Math.min(a[1], b[1]);\n\tlet maxY = Math.max(a[1], b[1]);\n\tfor (const axis of [0, 1] as const) {\n\t\tfor (const t of segmentExtrema(seg, axis)) {\n\t\t\tconst p = segmentPoint(seg, t);\n\t\t\tif (axis === 0) {\n\t\t\t\tminX = Math.min(minX, p[0]);\n\t\t\t\tmaxX = Math.max(maxX, p[0]);\n\t\t\t} else {\n\t\t\t\tminY = Math.min(minY, p[1]);\n\t\t\t\tmaxY = Math.max(maxY, p[1]);\n\t\t\t}\n\t\t}\n\t}\n\treturn { minX, minY, maxX, maxY };\n}\n\n/** Parameters splitting the segment into pieces monotone in x and y. */\nexport function monotoneParams(seg: BezierSegment): number[] {\n\tif (seg.length === 2) return [];\n\tconst ts = [...segmentExtrema(seg, 0), ...segmentExtrema(seg, 1)].sort((a, b) => a - b);\n\tconst out: number[] = [];\n\tfor (const t of ts) if (out.length === 0 || t - out[out.length - 1] > 1e-9) out.push(t);\n\treturn out;\n}\n\n/**\n * Signed area swept by the segment relative to the origin (½ ∫ x dy − y dx). Exact:\n * the integrand is a polynomial of degree at most 5, which 3-point Gauss–Legendre\n * integrates exactly.\n */\nexport function segmentAreaTerm(seg: BezierSegment): number {\n\tif (seg.length === 2) return (seg[0][0] * seg[1][1] - seg[1][0] * seg[0][1]) / 2;\n\tconst nodes = [-Math.sqrt(3 / 5), 0, Math.sqrt(3 / 5)];\n\tconst weights = [5 / 9, 8 / 9, 5 / 9];\n\tlet sum = 0;\n\tfor (let i = 0; i < 3; i++) {\n\t\tconst t = (nodes[i] + 1) / 2;\n\t\tconst p = segmentPoint(seg, t);\n\t\tconst d = segmentDerivative(seg, t);\n\t\tsum += weights[i] * (p[0] * d[1] - p[1] * d[0]);\n\t}\n\t// ½ for the area, ½ for mapping [-1, 1] onto [0, 1].\n\treturn sum / 4;\n}\n\n// Gauss–Legendre nodes / weights (order 8) on [-1, 1].\nconst GL_X = [\n\t-0.9602898564975363, -0.7966664774136267, -0.525532409916329, -0.1834346424956498, 0.1834346424956498,\n\t0.525532409916329, 0.7966664774136267, 0.9602898564975363\n];\nconst GL_W = [\n\t0.1012285362903763, 0.2223810344533745, 0.3137066458778873, 0.362683783378362, 0.362683783378362,\n\t0.3137066458778873, 0.2223810344533745, 0.1012285362903763\n];\n\nfunction glLength(seg: BezierSegment, t0: number, t1: number): number {\n\tconst half = (t1 - t0) / 2;\n\tconst mid = (t0 + t1) / 2;\n\tlet sum = 0;\n\tfor (let i = 0; i < GL_X.length; i++) {\n\t\tconst d = segmentDerivative(seg, mid + half * GL_X[i]);\n\t\tsum += GL_W[i] * Math.hypot(d[0], d[1]);\n\t}\n\treturn sum * half;\n}\n\nfunction adaptiveLength(seg: BezierSegment, t0: number, t1: number, whole: number, tol: number, depth: number): number {\n\tconst m = (t0 + t1) / 2;\n\tconst left = glLength(seg, t0, m);\n\tconst right = glLength(seg, m, t1);\n\tif (depth >= 18 || Math.abs(left + right - whole) <= tol) return left + right;\n\treturn adaptiveLength(seg, t0, m, left, tol / 2, depth + 1) + adaptiveLength(seg, m, t1, right, tol / 2, depth + 1);\n}\n\n/** Arc length of the segment between `t0` and `t1` (default the whole segment). */\nexport function segmentLength(seg: BezierSegment, t0 = 0, t1 = 1, tolerance = 1e-9): number {\n\tif (t1 < t0) return segmentLength(seg, t1, t0, tolerance);\n\tif (seg.length === 2) return dist(seg[0], seg[1]) * (t1 - t0);\n\tconst whole = glLength(seg, t0, t1);\n\treturn adaptiveLength(seg, t0, t1, whole, Math.max(tolerance, whole * 1e-12), 0);\n}\n\n/** Parameter at arc length `length` from the segment start (clamped to [0, 1]). */\nexport function segmentParamAtLength(seg: BezierSegment, length: number, total = segmentLength(seg)): number {\n\tif (!(total > 0) || length <= 0) return 0;\n\tif (length >= total) return 1;\n\tif (seg.length === 2) return length / total;\n\tlet lo = 0;\n\tlet hi = 1;\n\tlet t = length / total;\n\tfor (let i = 0; i < 40; i++) {\n\t\tconst err = segmentLength(seg, 0, t) - length;\n\t\tif (Math.abs(err) <= 1e-10 * Math.max(1, total)) break;\n\t\tif (err > 0) hi = t;\n\t\telse lo = t;\n\t\tconst d = segmentDerivative(seg, t);\n\t\tconst speed = Math.hypot(d[0], d[1]);\n\t\tlet next = speed > 0 ? t - err / speed : (lo + hi) / 2;\n\t\tif (!(next > lo && next < hi)) next = (lo + hi) / 2;\n\t\tt = next;\n\t}\n\treturn t;\n}\n\n/**\n * Nearest point of the segment to `p`: the parameter and the distance. Coarse sampling\n * followed by Newton refinement around the best samples.\n */\nexport function segmentNearest(seg: BezierSegment, p: Point): { t: number; distance: number } {\n\tif (seg.length === 2) {\n\t\tconst d = sub(seg[1], seg[0]);\n\t\tconst len2 = dot(d, d);\n\t\tconst t = len2 === 0 ? 0 : Math.max(0, Math.min(1, dot(sub(p, seg[0]), d) / len2));\n\t\treturn { t, distance: dist(p, segmentPoint(seg, t)) };\n\t}\n\tconst samples = 16;\n\tlet best = { t: 0, distance: dist(p, seg[0]) };\n\tconst candidates: number[] = [];\n\tlet prev = Infinity;\n\tlet prevT = 0;\n\tlet descending = false;\n\tfor (let i = 0; i <= samples; i++) {\n\t\tconst t = i / samples;\n\t\tconst d = dist(p, segmentPoint(seg, t));\n\t\tif (d < best.distance) best = { t, distance: d };\n\t\tif (i > 0 && d > prev && descending) candidates.push(prevT);\n\t\tdescending = d < prev;\n\t\tprev = d;\n\t\tprevT = t;\n\t}\n\tcandidates.push(best.t, 1);\n\tfor (const start of candidates) {\n\t\tlet t = start;\n\t\tfor (let k = 0; k < 12; k++) {\n\t\t\tconst q = segmentPoint(seg, t);\n\t\t\tconst d1 = segmentDerivative(seg, t);\n\t\t\tconst d2 = segmentSecondDerivative(seg, t);\n\t\t\tconst diff = sub(q, p);\n\t\t\tconst f = dot(diff, d1);\n\t\t\tconst fp = dot(d1, d1) + dot(diff, d2);\n\t\t\tif (!(Math.abs(fp) > 1e-300)) break;\n\t\t\tconst next = Math.max(0, Math.min(1, t - f / fp));\n\t\t\tif (Math.abs(next - t) < 1e-14) {\n\t\t\t\tt = next;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tt = next;\n\t\t}\n\t\tconst d = dist(p, segmentPoint(seg, t));\n\t\tif (d < best.distance) best = { t, distance: d };\n\t}\n\treturn best;\n}\n\n// ---------------------------------------------------------------------------\n// Arcs\n// ---------------------------------------------------------------------------\n\n/** Converts an SVG elliptical arc (SVG 1.1 F.6.5) to cubic segments, at most 90° each. */\nexport function arcToCubics(\n\tfrom: Point,\n\tcmd: { rx: number; ry: number; rotation: number; largeArc: boolean; sweep: boolean; x: number; y: number }\n): Point[][] {\n\tconst [x0, y0] = from;\n\tconst { x, y } = cmd;\n\tif (x0 === x && y0 === y) return [];\n\tlet rx = Math.abs(cmd.rx);\n\tlet ry = Math.abs(cmd.ry);\n\tif (rx === 0 || ry === 0) return [[from, [x, y]]];\n\tconst phi = (cmd.rotation * Math.PI) / 180;\n\tconst cos = Math.cos(phi);\n\tconst sin = Math.sin(phi);\n\tconst dx = (x0 - x) / 2;\n\tconst dy = (y0 - y) / 2;\n\tconst x1p = cos * dx + sin * dy;\n\tconst y1p = -sin * dx + cos * dy;\n\tconst lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);\n\tif (lambda > 1) {\n\t\tconst s = Math.sqrt(lambda);\n\t\trx *= s;\n\t\try *= s;\n\t}\n\tconst num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;\n\tconst den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;\n\tlet coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));\n\tif (cmd.largeArc === cmd.sweep) coef = -coef;\n\tconst cxp = (coef * rx * y1p) / ry;\n\tconst cyp = (-coef * ry * x1p) / rx;\n\tconst cx = cos * cxp - sin * cyp + (x0 + x) / 2;\n\tconst cy = sin * cxp + cos * cyp + (y0 + y) / 2;\n\tconst angle = (ux: number, uy: number, vx: number, vy: number) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);\n\tconst theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);\n\tlet delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);\n\tif (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;\n\telse if (cmd.sweep && delta < 0) delta += 2 * Math.PI;\n\tconst n = Math.max(1, Math.ceil(Math.abs(delta) / (Math.PI / 2) - 1e-9));\n\tconst step = delta / n;\n\tconst k = (4 / 3) * Math.tan(step / 4);\n\tconst map = (ux: number, uy: number): Point => {\n\t\tconst ex = rx * ux;\n\t\tconst ey = ry * uy;\n\t\treturn [cos * ex - sin * ey + cx, sin * ex + cos * ey + cy];\n\t};\n\tconst out: Point[][] = [];\n\tlet prev: Point = from;\n\tfor (let i = 0; i < n; i++) {\n\t\tconst a0 = theta1 + step * i;\n\t\tconst a1 = a0 + step;\n\t\tconst c0 = Math.cos(a0);\n\t\tconst s0 = Math.sin(a0);\n\t\tconst c1 = Math.cos(a1);\n\t\tconst s1 = Math.sin(a1);\n\t\tconst end: Point = i === n - 1 ? [x, y] : map(c1, s1);\n\t\tout.push([prev, map(c0 - k * s0, s0 + k * c0), map(c1 + k * s1, s1 - k * c1), end]);\n\t\tprev = end;\n\t}\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Conversions\n// ---------------------------------------------------------------------------\n\nfunction isBezierCommandList(input: BezierPathInput): input is readonly BezierCommand[] {\n\treturn Array.isArray(input) && !input.some((c: any) => c && c.type === 'A');\n}\n\n/**\n * Converts SVG path data or parsed commands to a `BezierPath` (absolute `M` / `L` / `Q` /\n * `C` / `Z`); elliptical arcs become cubic Béziers (at most 90° each).\n */\nexport function toBezierPath(input: BezierPathInput): BezierPath {\n\tconst commands: readonly (PathCommand | BezierCommand)[] =\n\t\ttypeof input === 'string' ? parseSvgPath(input) : isBezierCommandList(input) ? input : (input as readonly PathCommand[]);\n\tconst out: BezierPath = [];\n\tlet current: Point = [0, 0];\n\tlet start: Point = [0, 0];\n\tlet open = false;\n\tconst ensureMove = () => {\n\t\tif (!open) {\n\t\t\tout.push({ type: 'M', x: start[0], y: start[1] });\n\t\t\tcurrent = start;\n\t\t\topen = true;\n\t\t}\n\t};\n\tfor (const cmd of commands) {\n\t\tswitch (cmd.type) {\n\t\t\tcase 'M':\n\t\t\t\tstart = [cmd.x, cmd.y];\n\t\t\t\tcurrent = start;\n\t\t\t\tout.push({ type: 'M', x: cmd.x, y: cmd.y });\n\t\t\t\topen = true;\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\tensureMove();\n\t\t\t\tout.push({ type: 'L', x: cmd.x, y: cmd.y });\n\t\t\t\tcurrent = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'Q':\n\t\t\t\tensureMove();\n\t\t\t\tout.push({ type: 'Q', x1: cmd.x1, y1: cmd.y1, x: cmd.x, y: cmd.y });\n\t\t\t\tcurrent = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\tensureMove();\n\t\t\t\tout.push({ type: 'C', x1: cmd.x1, y1: cmd.y1, x2: cmd.x2, y2: cmd.y2, x: cmd.x, y: cmd.y });\n\t\t\t\tcurrent = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'A': {\n\t\t\t\tensureMove();\n\t\t\t\tfor (const c of arcToCubics(current, cmd)) {\n\t\t\t\t\tif (c.length === 2) out.push({ type: 'L', x: c[1][0], y: c[1][1] });\n\t\t\t\t\telse out.push({ type: 'C', x1: c[1][0], y1: c[1][1], x2: c[2][0], y2: c[2][1], x: c[3][0], y: c[3][1] });\n\t\t\t\t}\n\t\t\t\tcurrent = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'Z':\n\t\t\t\tif (open) out.push({ type: 'Z' });\n\t\t\t\topen = false;\n\t\t\t\tcurrent = start;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Splits a path into contours of segments. Zero-length segments are dropped. */\nexport function pathToContours(input: BezierPathInput): BezierContour[] {\n\tconst path = toBezierPath(input);\n\tconst contours: BezierContour[] = [];\n\tlet current: BezierContour | null = null;\n\tlet pen: Point = [0, 0];\n\tlet start: Point = [0, 0];\n\tconst push = (seg: Point[]) => {\n\t\tif (!current) {\n\t\t\tcurrent = { segments: [], closed: false };\n\t\t\tcontours.push(current);\n\t\t}\n\t\tif (seg.every((p) => samePoint(p, seg[0]))) return;\n\t\tcurrent.segments.push(seg);\n\t};\n\tfor (const cmd of path) {\n\t\tswitch (cmd.type) {\n\t\t\tcase 'M':\n\t\t\t\tstart = [cmd.x, cmd.y];\n\t\t\t\tpen = start;\n\t\t\t\tcurrent = { segments: [], closed: false };\n\t\t\t\tcontours.push(current);\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\tpush([pen, [cmd.x, cmd.y]]);\n\t\t\t\tpen = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'Q':\n\t\t\t\tpush([pen, [cmd.x1, cmd.y1], [cmd.x, cmd.y]]);\n\t\t\t\tpen = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\tpush([pen, [cmd.x1, cmd.y1], [cmd.x2, cmd.y2], [cmd.x, cmd.y]]);\n\t\t\t\tpen = [cmd.x, cmd.y];\n\t\t\t\tbreak;\n\t\t\tcase 'Z':\n\t\t\t\tif (current) (current as BezierContour).closed = true;\n\t\t\t\tcurrent = null;\n\t\t\t\tpen = start;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn contours;\n}\n\n/** Builds a path from contours (a closed contour's closing line is left to `Z`). */\nexport function contoursToPath(contours: readonly BezierContour[]): BezierPath {\n\tconst out: BezierPath = [];\n\tfor (const contour of contours) {\n\t\tconst segs = contour.segments;\n\t\tif (segs.length === 0) continue;\n\t\tout.push({ type: 'M', x: segs[0][0][0], y: segs[0][0][1] });\n\t\tlet count = segs.length;\n\t\tif (contour.closed && count > 1) {\n\t\t\tconst last = segs[count - 1];\n\t\t\tif (last.length === 2 && samePoint(last[1], segs[0][0])) count--;\n\t\t}\n\t\tfor (let i = 0; i < count; i++) {\n\t\t\tconst s = segs[i];\n\t\t\tconst end = s[s.length - 1];\n\t\t\tif (s.length === 2) out.push({ type: 'L', x: end[0], y: end[1] });\n\t\t\telse if (s.length === 3) out.push({ type: 'Q', x1: s[1][0], y1: s[1][1], x: end[0], y: end[1] });\n\t\t\telse out.push({ type: 'C', x1: s[1][0], y1: s[1][1], x2: s[2][0], y2: s[2][1], x: end[0], y: end[1] });\n\t\t}\n\t\tif (contour.closed) out.push({ type: 'Z' });\n\t}\n\treturn out;\n}\n\nexport interface BezierPathToSvgOptions {\n\t/** Decimal places (default 3). */\n\tprecision?: number;\n\t/** Added to every point before formatting. */\n\toffset?: { x: number; y: number };\n}\n\n/** Writes a path as compact SVG path data. */\nexport function bezierPathToSvg(input: BezierPathInput, options: BezierPathToSvgOptions = {}): string {\n\tconst path = toBezierPath(input);\n\tconst precision = options.precision ?? 3;\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tconst fx = (n: number) => formatNumber(n + dx, precision);\n\tconst fy = (n: number) => formatNumber(n + dy, precision);\n\tlet d = '';\n\tfor (const c of path) {\n\t\tswitch (c.type) {\n\t\t\tcase 'M':\n\t\t\t\td += `M${fx(c.x)} ${fy(c.y)}`;\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\td += `L${fx(c.x)} ${fy(c.y)}`;\n\t\t\t\tbreak;\n\t\t\tcase 'Q':\n\t\t\t\td += `Q${fx(c.x1)} ${fy(c.y1)} ${fx(c.x)} ${fy(c.y)}`;\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\td += `C${fx(c.x1)} ${fy(c.y1)} ${fx(c.x2)} ${fy(c.y2)} ${fx(c.x)} ${fy(c.y)}`;\n\t\t\t\tbreak;\n\t\t\tcase 'Z':\n\t\t\t\td += 'Z';\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn d;\n}\n\n/** 2D affine matrix `[a, b, c, d, e, f]` mapping `(x, y)` to `(a x + c y + e, b x + d y + f)`. */\nexport type AffineMatrix = readonly [number, number, number, number, number, number];\n\n/** Applies an affine transform to every point (exact for Bézier curves). */\nexport function transformBezierPath(input: BezierPathInput, m: AffineMatrix): BezierPath {\n\tconst [a, b, c, d, e, f] = m;\n\tconst X = (x: number, y: number) => a * x + c * y + e;\n\tconst Y = (x: number, y: number) => b * x + d * y + f;\n\treturn toBezierPath(input).map((cmd): BezierCommand => {\n\t\tswitch (cmd.type) {\n\t\t\tcase 'M':\n\t\t\tcase 'L':\n\t\t\t\treturn { type: cmd.type, x: X(cmd.x, cmd.y), y: Y(cmd.x, cmd.y) };\n\t\t\tcase 'Q':\n\t\t\t\treturn { type: 'Q', x1: X(cmd.x1, cmd.y1), y1: Y(cmd.x1, cmd.y1), x: X(cmd.x, cmd.y), y: Y(cmd.x, cmd.y) };\n\t\t\tcase 'C':\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'C',\n\t\t\t\t\tx1: X(cmd.x1, cmd.y1),\n\t\t\t\t\ty1: Y(cmd.x1, cmd.y1),\n\t\t\t\t\tx2: X(cmd.x2, cmd.y2),\n\t\t\t\t\ty2: Y(cmd.x2, cmd.y2),\n\t\t\t\t\tx: X(cmd.x, cmd.y),\n\t\t\t\t\ty: Y(cmd.x, cmd.y)\n\t\t\t\t};\n\t\t\tcase 'Z':\n\t\t\t\treturn cmd;\n\t\t}\n\t});\n}\n\n/** Number of drawing segments (`L` / `Q` / `C`, plus the closing line of `Z` when it has length). */\nexport function countPathSegments(input: BezierPathInput): { lines: number; quads: number; cubics: number } {\n\tconst out = { lines: 0, quads: 0, cubics: 0 };\n\tfor (const contour of pathToContours(input)) {\n\t\tconst segs = closeContour(contour);\n\t\tfor (const s of segs) {\n\t\t\tif (s.length === 2) out.lines++;\n\t\t\telse if (s.length === 3) out.quads++;\n\t\t\telse out.cubics++;\n\t\t}\n\t}\n\treturn out;\n}\n\n/** The contour's segments, with the closing line added when the contour is closed (or filled). */\nexport function closeContour(contour: BezierContour, force = false): BezierSegment[] {\n\tconst segs = contour.segments;\n\tif (segs.length === 0) return [];\n\tif (!contour.closed && !force) return segs;\n\tconst first = segs[0][0];\n\tconst lastSeg = segs[segs.length - 1];\n\tconst last = lastSeg[lastSeg.length - 1];\n\treturn samePoint(first, last) ? segs : [...segs, [last, first]];\n}\n\n// ---------------------------------------------------------------------------\n// Measures\n// ---------------------------------------------------------------------------\n\n/** Exact bounds of a path, or `null` when it has no points. */\nexport function bezierPathBounds(input: BezierPathInput): { minX: number; minY: number; maxX: number; maxY: number } | null {\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tconst path = toBezierPath(input);\n\tlet pen: Point | null = null;\n\tfor (const contour of pathToContours(path)) {\n\t\tfor (const s of contour.segments) {\n\t\t\tconst b = segmentBounds(s);\n\t\t\tminX = Math.min(minX, b.minX);\n\t\t\tminY = Math.min(minY, b.minY);\n\t\t\tmaxX = Math.max(maxX, b.maxX);\n\t\t\tmaxY = Math.max(maxY, b.maxY);\n\t\t}\n\t}\n\tfor (const c of path) {\n\t\tif (c.type === 'M') pen = [c.x, c.y];\n\t\tif (pen) {\n\t\t\tminX = Math.min(minX, pen[0]);\n\t\t\tminY = Math.min(minY, pen[1]);\n\t\t\tmaxX = Math.max(maxX, pen[0]);\n\t\t\tmaxY = Math.max(maxY, pen[1]);\n\t\t}\n\t}\n\treturn minX === Infinity ? null : { minX, minY, maxX, maxY };\n}\n\n/**\n * Signed area of the path as drawn: every subpath closed, contributions summed (positive\n * for counter-clockwise subpaths in a y-up frame). Exact for curves. Overlapping subpaths\n * count with their multiplicity; use `bezierPathArea` for the filled area.\n */\nexport function bezierPathSignedArea(input: BezierPathInput): number {\n\tlet sum = 0;\n\tfor (const contour of pathToContours(input)) sum += contourSignedArea(closeContour(contour, true));\n\treturn sum;\n}\n\n/** Signed area of a closed chain of segments. */\nexport function contourSignedArea(segs: readonly BezierSegment[]): number {\n\tlet sum = 0;\n\tfor (const s of segs) sum += segmentAreaTerm(s);\n\treturn sum;\n}\n\n/** Arc length of a path (closing lines of closed subpaths included). */\nexport function bezierPathLength(input: BezierPathInput): number {\n\tlet total = 0;\n\tfor (const contour of pathToContours(input)) for (const s of closeContour(contour)) total += segmentLength(s);\n\treturn total;\n}\n\n/** Point, unit tangent and angle (radians) at an arc length along the path. */\nexport interface PathPointAtLength {\n\tpoint: Point;\n\ttangent: Point;\n\tangle: number;\n\t/** Subpath index. */\n\tcontour: number;\n\t/** Segment index within the subpath (closing line last). */\n\tsegment: number;\n\t/** Parameter within the segment. */\n\tt: number;\n}\n\n/**\n * The point at arc length `length` from the start of the path (clamped to the path). Arc\n * lengths run through the subpaths in order, as `bezierPathLength` counts them.\n */\nexport function bezierPathPointAtLength(input: BezierPathInput, length: number): PathPointAtLength | null {\n\tconst contours = pathToContours(input);\n\tlet remaining = Math.max(0, length);\n\tlet last: PathPointAtLength | null = null;\n\tfor (let ci = 0; ci < contours.length; ci++) {\n\t\tconst segs = closeContour(contours[ci]);\n\t\tfor (let si = 0; si < segs.length; si++) {\n\t\t\tconst s = segs[si];\n\t\t\tconst len = segmentLength(s);\n\t\t\tif (remaining <= len) {\n\t\t\t\tconst t = segmentParamAtLength(s, remaining, len);\n\t\t\t\tconst tangent = segmentTangent(s, t);\n\t\t\t\treturn { point: segmentPoint(s, t), tangent, angle: Math.atan2(tangent[1], tangent[0]), contour: ci, segment: si, t };\n\t\t\t}\n\t\t\tremaining -= len;\n\t\t\tconst tangent = segmentTangent(s, 1);\n\t\t\tlast = { point: s[s.length - 1], tangent, angle: Math.atan2(tangent[1], tangent[0]), contour: ci, segment: si, t: 1 };\n\t\t}\n\t}\n\treturn last;\n}\n\n/**\n * Splits a segment chain by arc length: the part between `from` and `to` (measured from the\n * chain's start) as new segments.\n */\nexport function trimSegments(segs: readonly BezierSegment[], from: number, to: number, lengths?: number[]): Point[][] {\n\tconst lens = lengths ?? segs.map((s) => segmentLength(s));\n\tconst out: Point[][] = [];\n\tlet at = 0;\n\tfor (let i = 0; i < segs.length; i++) {\n\t\tconst len = lens[i];\n\t\tconst s0 = at;\n\t\tconst s1 = at + len;\n\t\tat = s1;\n\t\tif (s1 <= from || s0 >= to || len <= 0) continue;\n\t\tconst t0 = from > s0 ? segmentParamAtLength(segs[i], from - s0, len) : 0;\n\t\tconst t1 = to < s1 ? segmentParamAtLength(segs[i], to - s0, len) : 1;\n\t\tif (t1 > t0) out.push(subSegment(segs[i], t0, t1));\n\t}\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Winding numbers\n// ---------------------------------------------------------------------------\n\n/** Parameter where a y-monotone segment reaches height `y` (it must span `y`). */\nexport function solveMonotone(seg: BezierSegment, axis: 0 | 1, value: number): number {\n\tconst a = seg[0][axis];\n\tconst b = seg[seg.length - 1][axis];\n\tif (seg.length === 2) return b === a ? 0 : (value - a) / (b - a);\n\tconst increasing = b > a;\n\tlet lo = 0;\n\tlet hi = 1;\n\tlet t = b === a ? 0.5 : Math.max(0, Math.min(1, (value - a) / (b - a)));\n\tfor (let i = 0; i < 60; i++) {\n\t\tconst v = segmentPoint(seg, t)[axis] - value;\n\t\tif (v === 0) return t;\n\t\tif (v > 0 === increasing) hi = t;\n\t\telse lo = t;\n\t\tconst d = segmentDerivative(seg, t)[axis];\n\t\tlet next = d !== 0 ? t - v / d : (lo + hi) / 2;\n\t\tif (!(next > lo && next < hi)) next = (lo + hi) / 2;\n\t\tif (Math.abs(next - t) < 1e-15) return next;\n\t\tt = next;\n\t}\n\treturn t;\n}\n\n/** Splits segments at their y extrema (pieces monotone in y). */\nfunction yMonotonePieces(segs: readonly BezierSegment[]): BezierSegment[] {\n\tconst out: BezierSegment[] = [];\n\tfor (const s of segs) {\n\t\tif (s.length === 2) {\n\t\t\tout.push(s);\n\t\t\tcontinue;\n\t\t}\n\t\tconst ts = segmentExtrema(s, 1);\n\t\tlet prev = 0;\n\t\tfor (const t of [...ts, 1]) {\n\t\t\tout.push(subSegment(s, prev, t));\n\t\t\tprev = t;\n\t\t}\n\t}\n\treturn out;\n}\n\n/**\n * Winding number of a set of closed segment chains around `point` (ray to +x, half-open\n * crossing rule). Points exactly on the outline may report either side.\n */\nexport function windingNumber(chains: readonly (readonly BezierSegment[])[], point: Point): number {\n\tconst [px, py] = point;\n\tlet winding = 0;\n\tfor (const chain of chains) {\n\t\tfor (const s of yMonotonePieces(chain)) {\n\t\t\tconst ay = s[0][1];\n\t\t\tconst by = s[s.length - 1][1];\n\t\t\tlet dir = 0;\n\t\t\tif (ay <= py && by > py) dir = 1;\n\t\t\telse if (by <= py && ay > py) dir = -1;\n\t\t\telse continue;\n\t\t\tlet minX = Infinity;\n\t\t\tlet maxX = -Infinity;\n\t\t\tfor (const p of s) {\n\t\t\t\tif (p[0] < minX) minX = p[0];\n\t\t\t\tif (p[0] > maxX) maxX = p[0];\n\t\t\t}\n\t\t\tif (minX > px) {\n\t\t\t\twinding += dir;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (maxX <= px) continue;\n\t\t\tconst t = solveMonotone(s, 1, py);\n\t\t\tif (segmentPoint(s, t)[0] > px) winding += dir;\n\t\t}\n\t}\n\treturn winding;\n}\n\n/** Whether `point` is inside the filled path (every subpath closed for filling). */\nexport function pointInBezierPath(input: BezierPathInput, point: Point, fillRule: PolygonFillRule = 'nonzero'): boolean {\n\tconst chains = pathToContours(input).map((c) => closeContour(c, true));\n\tconst w = windingNumber(chains, point);\n\treturn fillRule === 'evenodd' ? (w & 1) === 1 : w !== 0;\n}\n\n/** Distance from `point` to the path outline (closing lines of closed subpaths included). */\nexport function distanceToBezierPath(input: BezierPathInput | readonly BezierContour[], point: Point): number {\n\tconst contours =\n\t\tArray.isArray(input) && input.length > 0 && (input[0] as BezierContour).segments\n\t\t\t? (input as readonly BezierContour[])\n\t\t\t: pathToContours(input as BezierPathInput);\n\tlet best = Infinity;\n\tfor (const contour of contours) {\n\t\tfor (const s of closeContour(contour)) {\n\t\t\t// Skip segments whose control hull is farther than the best distance so far.\n\t\t\tlet minX = Infinity;\n\t\t\tlet minY = Infinity;\n\t\t\tlet maxX = -Infinity;\n\t\t\tlet maxY = -Infinity;\n\t\t\tfor (const p of s) {\n\t\t\t\tminX = Math.min(minX, p[0]);\n\t\t\t\tminY = Math.min(minY, p[1]);\n\t\t\t\tmaxX = Math.max(maxX, p[0]);\n\t\t\t\tmaxY = Math.max(maxY, p[1]);\n\t\t\t}\n\t\t\tconst dx = Math.max(minX - point[0], 0, point[0] - maxX);\n\t\t\tconst dy = Math.max(minY - point[1], 0, point[1] - maxY);\n\t\t\tif (Math.hypot(dx, dy) >= best) continue;\n\t\t\tbest = Math.min(best, segmentNearest(s, point).distance);\n\t\t}\n\t\tif (contour.segments.length === 0) continue;\n\t}\n\treturn best;\n}\n\n/** Flattens a closed chain into a polygon ring (for classification only). */\nexport function flattenChain(segs: readonly BezierSegment[], tolerance: number): Point[] {\n\tconst out: Point[] = [];\n\tfor (const s of segs) {\n\t\tif (out.length === 0) out.push(s[0]);\n\t\tif (s.length === 2) {\n\t\t\tout.push(s[1]);\n\t\t\tcontinue;\n\t\t}\n\t\tlet dd = 0;\n\t\tfor (let i = 0; i + 2 < s.length; i++) {\n\t\t\tdd = Math.max(dd, Math.hypot(s[i][0] - 2 * s[i + 1][0] + s[i + 2][0], s[i][1] - 2 * s[i + 1][1] + s[i + 2][1]));\n\t\t}\n\t\tconst deg = s.length - 1;\n\t\tconst n = Math.max(1, Math.min(256, Math.ceil(Math.sqrt((deg * (deg - 1) * dd) / (8 * tolerance)))));\n\t\tfor (let k = 1; k <= n; k++) out.push(segmentPoint(s, k / n));\n\t}\n\tif (out.length > 1 && samePoint(out[0], out[out.length - 1])) out.pop();\n\treturn out;\n}\n","/**\n * Curve-preserving boolean operations (union, intersection, difference, xor and divide) on\n * Bézier paths.\n *\n * Algorithm (planar arrangement of curve pieces + winding classification):\n * 1. Every segment of every operand (open subpaths closed for filling) is a *source*; it is\n * cut into pieces monotone in x and y. Each piece remembers its source and its parameter\n * range there, so results can be rebuilt from exact sub-curves of the inputs.\n * 2. Pieces are intersected pairwise (pruned by a sweep on x): lines analytically, curves by\n * recursive subdivision down to flat sub-curves, chord intersection and Newton polishing.\n * Endpoints lying on another piece (T-junctions, tangent touches), collinear / coincident\n * overlaps and clusters of hits along a tangency are resolved first. Points closer than\n * `epsilon` share one vertex. Splitting repeats until no piece crosses another.\n * 3. Coincident pieces (shared edges, identical curves) merge into one edge carrying every\n * operand's signed multiplicity.\n * 4. Each edge is classified by casting a horizontal or vertical ray from its midpoint (the\n * pieces are monotone, so a ray crosses each at most once); the operands' fill rules turn\n * the winding numbers on both sides into inside flags and the operation picks the edges\n * with the result on exactly one side.\n * 5. The kept edges are traced into rings, taking the sharpest left turn at each vertex\n * (ties between tangent curves broken by a short chord along each curve), and consecutive\n * pieces of one source are merged back into one exact sub-curve, so circles stay four\n * cubics and rectangles four lines.\n *\n * When the curve engine fails (non-converging intersections on near-coincident curves,\n * an inconsistent arrangement) the operation falls back to the polygon engine\n * (`polygonBoolean`) on flattened input and reports it (`engine: 'polygon'`).\n *\n * Portions ported from vectorcraft-main/crates/pathops/src/boolean.rs (MIT OR Apache-2.0,\n * © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from '../types.js';\nimport {\n\tpolygonBoolean,\n\tdividePolygons,\n\ttype MultiPolygon,\n\ttype PolygonBooleanOperation,\n\ttype PolygonFillRule\n} from '../polygonBoolean.js';\nimport { polygonToSvgPath } from '../shapeGeometry.js';\nimport {\n\tcloseContour,\n\tcontourSignedArea,\n\tcontoursToPath,\n\tcross,\n\tdist,\n\tdot,\n\tflattenChain,\n\tisStraightSegment,\n\tmonotoneParams,\n\tpathToContours,\n\tsegmentDerivative,\n\tsegmentLength,\n\tsegmentNearest,\n\tsegmentPoint,\n\tsegmentTangent,\n\tsolveMonotone,\n\tsub,\n\tsubSegment,\n\ttoBezierPath,\n\twindingNumber,\n\ttype BezierContour,\n\ttype BezierPath,\n\ttype BezierPathInput,\n\ttype BezierSegment\n} from './bezier.js';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Boolean operations (same names as `polygonBoolean`). `xor` is Exclude. */\nexport type PathBooleanOperation = PolygonBooleanOperation;\n\n/** A path with the fill rule it is read with (default `nonzero`). */\nexport interface PathOperand {\n\tpath: BezierPathInput;\n\tfillRule?: PolygonFillRule;\n}\n\nexport type PathOperandInput = BezierPathInput | PathOperand;\n\nexport interface PathOpsOptions {\n\t/**\n\t * Distance below which points are treated as equal. Defaults to `1e-9` times the\n\t * largest coordinate magnitude (at least `1e-9`).\n\t */\n\tepsilon?: number;\n\t/** Fall back to the polygon engine when the curve engine fails (default `true`). */\n\tfallback?: boolean;\n\t/** Flattening tolerance used by the polygon fallback (default 0.05). */\n\tfallbackTolerance?: number;\n\t/** Called with the reason whenever the polygon fallback is used. */\n\tonFallback?: (reason: string) => void;\n\t/**\n\t * Work budget of the curve–curve intersection search (flat sub-curve pairs per pair of\n\t * pieces, default 20,000). Exceeding it (nearly coincident curves) falls back.\n\t */\n\tsubdivisionBudget?: number;\n}\n\n/** Which engine produced a result, and why the curve engine was not used. */\nexport interface PathOpsReport {\n\tengine: 'curve' | 'polygon';\n\tfallbackReason?: string;\n\t/** Set when the input cannot be processed at all (non-finite coordinates); the result is empty. */\n\terror?: string;\n}\n\nexport interface PathBooleanResult extends PathOpsReport {\n\tpath: BezierPath;\n}\n\n/** One region of `dividePaths`, with the indices of the operands that cover it. */\nexport interface PathPiece {\n\tpath: BezierPath;\n\toperands: number[];\n}\n\nexport interface DividePathsResult extends PathOpsReport {\n\tpieces: PathPiece[];\n}\n\n/** Raised by the curve engine when it cannot produce a reliable arrangement. */\nexport class PathOpsError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'PathOpsError';\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Operands\n// ---------------------------------------------------------------------------\n\ninterface NormalizedOperand {\n\tchains: BezierSegment[][];\n\tfillRule: PolygonFillRule;\n}\n\nfunction isOperandObject(input: PathOperandInput): input is PathOperand {\n\treturn !!input && typeof input === 'object' && !Array.isArray(input) && 'path' in input;\n}\n\nfunction normalizeOperand(input: PathOperandInput): NormalizedOperand {\n\tconst path = isOperandObject(input) ? input.path : input;\n\tconst fillRule = isOperandObject(input) ? input.fillRule ?? 'nonzero' : 'nonzero';\n\tconst chains: BezierSegment[][] = [];\n\tfor (const contour of pathToContours(path ?? '')) {\n\t\tconst chain = closeContour(contour, true);\n\t\tif (chain.length === 0) continue;\n\t\tfor (const seg of chain) {\n\t\t\tfor (const p of seg) {\n\t\t\t\tif (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) throw new PathOpsError(NON_FINITE);\n\t\t\t}\n\t\t}\n\t\tchains.push(chain);\n\t}\n\treturn { chains, fillRule };\n}\n\nfunction scaleOf(operandChains: readonly (readonly (readonly BezierSegment[])[])[]): number {\n\tlet max = 0;\n\tfor (const chains of operandChains) {\n\t\tfor (const chain of chains) {\n\t\t\tfor (const seg of chain) {\n\t\t\t\tfor (const [x, y] of seg) max = Math.max(max, Math.abs(x), Math.abs(y));\n\t\t\t}\n\t\t}\n\t}\n\treturn Math.max(1, max);\n}\n\n// ---------------------------------------------------------------------------\n// Arrangement data\n// ---------------------------------------------------------------------------\n\ninterface Vertex {\n\tid: number;\n\tx: number;\n\ty: number;\n\tp: Point;\n}\n\n/** Merges points closer than `eps` (grid hash with neighbour lookup). */\nclass VertexPool {\n\treadonly vertices: Vertex[] = [];\n\tprivate readonly cells = new Map<string, number[]>();\n\tconstructor(private readonly eps: number) {}\n\n\tget(x: number, y: number): Vertex {\n\t\tconst cx = Math.floor(x / this.eps);\n\t\tconst cy = Math.floor(y / this.eps);\n\t\tlet best: Vertex | null = null;\n\t\tlet bestDist = Infinity;\n\t\tfor (let dx = -1; dx <= 1; dx++) {\n\t\t\tfor (let dy = -1; dy <= 1; dy++) {\n\t\t\t\tconst ids = this.cells.get(`${cx + dx},${cy + dy}`);\n\t\t\t\tif (!ids) continue;\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tconst v = this.vertices[id];\n\t\t\t\t\tconst d = Math.hypot(v.x - x, v.y - y);\n\t\t\t\t\tif (d <= this.eps && d < bestDist) {\n\t\t\t\t\t\tbest = v;\n\t\t\t\t\t\tbestDist = d;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (best) return best;\n\t\tconst vertex: Vertex = { id: this.vertices.length, x, y, p: [x, y] };\n\t\tthis.vertices.push(vertex);\n\t\tconst key = `${cx},${cy}`;\n\t\tconst bucket = this.cells.get(key);\n\t\tif (bucket) bucket.push(vertex.id);\n\t\telse this.cells.set(key, [vertex.id]);\n\t\treturn vertex;\n\t}\n}\n\ninterface Source {\n\tseg: BezierSegment;\n\tline: boolean;\n}\n\ninterface Piece {\n\tpts: Point[];\n\ta: Vertex;\n\tb: Vertex;\n\toperand: number;\n\tsrc: number;\n\t/** Source parameters at `a` and `b` (`s0 < s1`). */\n\ts0: number;\n\ts1: number;\n\tdirty: boolean;\n\tminX: number;\n\tmaxX: number;\n\tminY: number;\n\tmaxY: number;\n}\n\nfunction hull(pts: readonly Point[]) {\n\tlet minX = Infinity;\n\tlet maxX = -Infinity;\n\tlet minY = Infinity;\n\tlet maxY = -Infinity;\n\tfor (const [x, y] of pts) {\n\t\tif (x < minX) minX = x;\n\t\tif (x > maxX) maxX = x;\n\t\tif (y < minY) minY = y;\n\t\tif (y > maxY) maxY = y;\n\t}\n\treturn { minX, maxX, minY, maxY };\n}\n\n/** Moves the end points of a segment onto two vertices, dragging the handles along. */\nfunction snapEnds(pts: readonly Point[], a: Point, b: Point): Point[] {\n\tconst n = pts.length;\n\tif (n === 2) return [a, b];\n\tconst d0 = sub(a, pts[0]);\n\tconst d1 = sub(b, pts[n - 1]);\n\tif (n === 3) {\n\t\treturn [a, [pts[1][0] + (d0[0] + d1[0]) / 2, pts[1][1] + (d0[1] + d1[1]) / 2], b];\n\t}\n\treturn [a, [pts[1][0] + d0[0], pts[1][1] + d0[1]], [pts[2][0] + d1[0], pts[2][1] + d1[1]], b];\n}\n\nfunction makePiece(\n\tpts: Point[],\n\ta: Vertex,\n\tb: Vertex,\n\toperand: number,\n\tsrc: number,\n\ts0: number,\n\ts1: number\n): Piece {\n\tconst snapped = snapEnds(pts, a.p, b.p);\n\treturn { pts: snapped, a, b, operand, src, s0, s1, dirty: true, ...hull(snapped) };\n}\n\n// ---------------------------------------------------------------------------\n// Intersections\n// ---------------------------------------------------------------------------\n\ninterface PairEvent {\n\ttp: number;\n\ttq: number;\n\tpoint: Point;\n\t/** Set for shared end points and end points lying on the other piece. */\n\tanchor?: boolean;\n\t/** Shared vertex: nothing to split. */\n\tshared?: boolean;\n}\n\ninterface IntersectContext {\n\teps: number;\n\tleaves: number;\n\tbudget: number;\n}\n\nconst NON_FINITE = 'non-finite coordinates';\n\nfunction flatness(pts: readonly Point[]): number {\n\tif (pts.length === 2) return 0;\n\tconst a = pts[0];\n\tconst b = pts[pts.length - 1];\n\tconst chord = sub(b, a);\n\tconst len = Math.hypot(chord[0], chord[1]);\n\tlet worst = 0;\n\tfor (let i = 1; i < pts.length - 1; i++) {\n\t\tconst v = sub(pts[i], a);\n\t\tconst d = len > 0 ? Math.abs(cross(chord, v)) / len : Math.hypot(v[0], v[1]);\n\t\tconst along = len > 0 ? dot(chord, v) / len : 0;\n\t\tconst over = Math.max(0, -along, along - len);\n\t\tworst = Math.max(worst, d, over);\n\t}\n\treturn worst;\n}\n\nfunction split2(pts: readonly Point[]): [Point[], Point[]] {\n\tconst n = pts.length;\n\tconst mid = (p: Point, q: Point): Point => [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];\n\tif (n === 2) {\n\t\tconst m = mid(pts[0], pts[1]);\n\t\treturn [\n\t\t\t[pts[0], m],\n\t\t\t[m, pts[1]]\n\t\t];\n\t}\n\tif (n === 3) {\n\t\tconst a = mid(pts[0], pts[1]);\n\t\tconst b = mid(pts[1], pts[2]);\n\t\tconst m = mid(a, b);\n\t\treturn [\n\t\t\t[pts[0], a, m],\n\t\t\t[m, b, pts[2]]\n\t\t];\n\t}\n\tconst a = mid(pts[0], pts[1]);\n\tconst b = mid(pts[1], pts[2]);\n\tconst c = mid(pts[2], pts[3]);\n\tconst ab = mid(a, b);\n\tconst bc = mid(b, c);\n\tconst m = mid(ab, bc);\n\treturn [\n\t\t[pts[0], a, ab, m],\n\t\t[m, bc, c, pts[3]]\n\t];\n}\n\n/** Closest points of two segments: parameters and distance. */\nfunction closestSegmentPoints(a0: Point, a1: Point, b0: Point, b1: Point): { s: number; u: number; d: number } {\n\tconst d1 = sub(a1, a0);\n\tconst d2 = sub(b1, b0);\n\tconst r = sub(a0, b0);\n\tconst a = dot(d1, d1);\n\tconst e = dot(d2, d2);\n\tconst f = dot(d2, r);\n\tlet s = 0;\n\tlet u = 0;\n\tif (a <= 1e-300 && e <= 1e-300) return { s: 0, u: 0, d: dist(a0, b0) };\n\tif (a <= 1e-300) {\n\t\tu = Math.max(0, Math.min(1, f / e));\n\t} else {\n\t\tconst c = dot(d1, r);\n\t\tif (e <= 1e-300) {\n\t\t\ts = Math.max(0, Math.min(1, -c / a));\n\t\t} else {\n\t\t\tconst b = dot(d1, d2);\n\t\t\tconst denom = a * e - b * b;\n\t\t\ts = denom > 0 ? Math.max(0, Math.min(1, (b * f - c * e) / denom)) : 0;\n\t\t\tu = (b * s + f) / e;\n\t\t\tif (u < 0) {\n\t\t\t\tu = 0;\n\t\t\t\ts = Math.max(0, Math.min(1, -c / a));\n\t\t\t} else if (u > 1) {\n\t\t\t\tu = 1;\n\t\t\t\ts = Math.max(0, Math.min(1, (b - c) / a));\n\t\t\t}\n\t\t}\n\t}\n\tconst pa: Point = [a0[0] + d1[0] * s, a0[1] + d1[1] * s];\n\tconst pb: Point = [b0[0] + d2[0] * u, b0[1] + d2[1] * u];\n\treturn { s, u, d: dist(pa, pb) };\n}\n\n/** Newton polishing of P(s) = Q(u). Returns the best parameters found. */\nfunction polish(P: readonly Point[], Q: readonly Point[], s: number, u: number): { s: number; u: number; d: number } {\n\tlet best = { s, u, d: dist(segmentPoint(P, s), segmentPoint(Q, u)) };\n\tfor (let i = 0; i < 10 && best.d > 0; i++) {\n\t\tconst f = sub(segmentPoint(P, s), segmentPoint(Q, u));\n\t\tconst dp = segmentDerivative(P, s);\n\t\tconst dq = segmentDerivative(Q, u);\n\t\t// Solve [dp, -dq] [ds, du]^T = -f\n\t\tconst det = cross(dp, [-dq[0], -dq[1]]);\n\t\tif (!(Math.abs(det) > 1e-300)) break;\n\t\tconst ds = cross([-f[0], -f[1]], [-dq[0], -dq[1]]) / det;\n\t\tconst du = cross(dp, [-f[0], -f[1]]) / det;\n\t\ts = Math.max(0, Math.min(1, s + ds));\n\t\tu = Math.max(0, Math.min(1, u + du));\n\t\tconst d = dist(segmentPoint(P, s), segmentPoint(Q, u));\n\t\tif (d < best.d) best = { s, u, d };\n\t\telse break;\n\t}\n\treturn best;\n}\n\nfunction intersectSubdivided(\n\tP: Point[],\n\tp0: number,\n\tp1: number,\n\tQ: Point[],\n\tq0: number,\n\tq1: number,\n\tfullP: readonly Point[],\n\tfullQ: readonly Point[],\n\tctx: IntersectContext,\n\tout: PairEvent[],\n\tdepth: number\n) {\n\tconst hp = hull(P);\n\tconst hq = hull(Q);\n\tconst e = ctx.eps;\n\tif (hp.minX > hq.maxX + e || hq.minX > hp.maxX + e || hp.minY > hq.maxY + e || hq.minY > hp.maxY + e) return;\n\tconst fp = flatness(P);\n\tconst fq = flatness(Q);\n\tconst flatTol = e * 0.5;\n\tif ((fp <= flatTol && fq <= flatTol) || depth >= 52) {\n\t\tif (++ctx.leaves > ctx.budget) throw new PathOpsError('curve intersection did not converge');\n\t\tconst a0 = P[0];\n\t\tconst a1 = P[P.length - 1];\n\t\tconst b0 = Q[0];\n\t\tconst b1 = Q[Q.length - 1];\n\t\tconst d1 = sub(a1, a0);\n\t\tconst d2 = sub(b1, b0);\n\t\tconst denom = cross(d1, d2);\n\t\tlet s = 0;\n\t\tlet u = 0;\n\t\tlet hit = false;\n\t\tconst l1 = Math.hypot(d1[0], d1[1]);\n\t\tconst l2 = Math.hypot(d2[0], d2[1]);\n\t\tif (Math.abs(denom) > 1e-12 * l1 * l2) {\n\t\t\tconst r = sub(b0, a0);\n\t\t\ts = cross(r, d2) / denom;\n\t\t\tu = cross(r, d1) / denom;\n\t\t\tconst ts = l1 > 0 ? e / l1 : 1;\n\t\t\tconst tu = l2 > 0 ? e / l2 : 1;\n\t\t\thit = s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu;\n\t\t}\n\t\tif (!hit) {\n\t\t\t// No crossing of the chords: a touch when they come within epsilon.\n\t\t\tconst c = closestSegmentPoints(a0, a1, b0, b1);\n\t\t\tif (c.d > e) return;\n\t\t\ts = c.s;\n\t\t\tu = c.u;\n\t\t}\n\t\ts = Math.max(0, Math.min(1, s));\n\t\tu = Math.max(0, Math.min(1, u));\n\t\tconst best = polish(fullP, fullQ, p0 + (p1 - p0) * s, q0 + (q1 - q0) * u);\n\t\tif (best.d > e * 2) return;\n\t\tconst a = segmentPoint(fullP, best.s);\n\t\tconst b = segmentPoint(fullQ, best.u);\n\t\tout.push({ tp: best.s, tq: best.u, point: [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2] });\n\t\treturn;\n\t}\n\tconst splitP = fp > flatTol;\n\tconst splitQ = fq > flatTol;\n\tconst pm = (p0 + p1) / 2;\n\tconst qm = (q0 + q1) / 2;\n\tif (splitP && splitQ) {\n\t\tconst [pa, pb] = split2(P);\n\t\tconst [qa, qb] = split2(Q);\n\t\tintersectSubdivided(pa, p0, pm, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);\n\t\tintersectSubdivided(pa, p0, pm, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);\n\t\tintersectSubdivided(pb, pm, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);\n\t\tintersectSubdivided(pb, pm, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);\n\t} else if (splitP) {\n\t\tconst [pa, pb] = split2(P);\n\t\tintersectSubdivided(pa, p0, pm, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);\n\t\tintersectSubdivided(pb, pm, p1, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);\n\t} else {\n\t\tconst [qa, qb] = split2(Q);\n\t\tintersectSubdivided(P, p0, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);\n\t\tintersectSubdivided(P, p0, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);\n\t}\n}\n\n/** A crossing or touch of two segments: the parameter on each and the point. */\nexport interface SegmentIntersection {\n\tta: number;\n\ttb: number;\n\tpoint: Point;\n}\n\n/**\n * Every point where two segments (lines, quadratics or cubics, as control points) cross or\n * touch, by the same subdivision search the boolean operations use: flat sub-curves, chord\n * intersection, Newton polishing. Hits closer than `epsilon` (default: 1e-9 of the segments'\n * size) are one. Coincident stretches report the hits the search finds along them.\n */\nexport function segmentIntersections(a: BezierSegment, b: BezierSegment, options: { epsilon?: number; budget?: number } = {}): SegmentIntersection[] {\n\tlet size = 1;\n\tfor (const [x, y] of [...a, ...b]) size = Math.max(size, Math.abs(x), Math.abs(y));\n\tconst eps = options.epsilon ?? size * 1e-9;\n\tconst found: PairEvent[] = [];\n\tconst ctx: IntersectContext = { eps, leaves: 0, budget: options.budget ?? 4096 };\n\ttry {\n\t\tintersectSubdivided(a.slice(), 0, 1, b.slice(), 0, 1, a, b, ctx, found, 0);\n\t} catch (error) {\n\t\tif (!(error instanceof PathOpsError)) throw error;\n\t}\n\tconst out: SegmentIntersection[] = [];\n\tfor (const ev of found.sort((x, y) => x.tp - y.tp)) {\n\t\tif (out.some((o) => dist(o.point, ev.point) <= eps * 4)) continue;\n\t\tout.push({ ta: ev.tp, tb: ev.tq, point: ev.point });\n\t}\n\treturn out;\n}\n\nfunction nearestOn(piece: Piece, p: Point, eps: number): { t: number; distance: number } | null {\n\tif (p[0] < piece.minX - eps || p[0] > piece.maxX + eps || p[1] < piece.minY - eps || p[1] > piece.maxY + eps) return null;\n\treturn segmentNearest(piece.pts, p);\n}\n\n/** Whether the two pieces stay within `tol` of each other between two events. */\nfunction staysClose(P: Piece, Q: Piece, e1: PairEvent, e2: PairEvent, tol: number): boolean {\n\tfor (const f of [0.25, 0.5, 0.75]) {\n\t\tconst tp = e1.tp + (e2.tp - e1.tp) * f;\n\t\tconst q = segmentNearest(Q.pts, segmentPoint(P.pts, tp));\n\t\tif (q.distance > tol) return false;\n\t}\n\treturn true;\n}\n\nfunction pairEvents(P: Piece, Q: Piece, ctx: IntersectContext): PairEvent[] {\n\tconst eps = ctx.eps;\n\tconst anchors: PairEvent[] = [];\n\t// Shared vertices.\n\tfor (const [va, tp] of [\n\t\t[P.a, 0],\n\t\t[P.b, 1]\n\t] as const) {\n\t\tif (va === Q.a) anchors.push({ tp, tq: 0, point: va.p, anchor: true, shared: true });\n\t\telse if (va === Q.b) anchors.push({ tp, tq: 1, point: va.p, anchor: true, shared: true });\n\t}\n\t// End points lying on the other piece (T-junctions, overlaps).\n\tfor (const [v, tq] of [\n\t\t[Q.a, 0],\n\t\t[Q.b, 1]\n\t] as const) {\n\t\tif (v === P.a || v === P.b) continue;\n\t\tconst near = nearestOn(P, v.p, eps);\n\t\tif (near && near.distance <= eps) anchors.push({ tp: near.t, tq, point: v.p, anchor: true });\n\t}\n\tfor (const [v, tp] of [\n\t\t[P.a, 0],\n\t\t[P.b, 1]\n\t] as const) {\n\t\tif (v === Q.a || v === Q.b) continue;\n\t\tconst near = nearestOn(Q, v.p, eps);\n\t\tif (near && near.distance <= eps) anchors.push({ tp, tq: near.t, point: v.p, anchor: true });\n\t}\n\n\t// Coincident overlap: two anchors with the pieces running together between them.\n\tif (anchors.length >= 2) {\n\t\tconst sorted = [...anchors].sort((x, y) => x.tp - y.tp);\n\t\tconst first = sorted[0];\n\t\tconst last = sorted[sorted.length - 1];\n\t\tif (dist(first.point, last.point) > eps && staysClose(P, Q, first, last, eps * 4)) {\n\t\t\treturn anchors.filter((a) => !a.shared);\n\t\t}\n\t}\n\n\tconst found: PairEvent[] = [];\n\tif (P.pts.length === 2 && Q.pts.length === 2) {\n\t\tconst a0 = P.pts[0];\n\t\tconst d1 = sub(P.pts[1], a0);\n\t\tconst b0 = Q.pts[0];\n\t\tconst d2 = sub(Q.pts[1], b0);\n\t\tconst denom = cross(d1, d2);\n\t\tconst l1 = Math.hypot(d1[0], d1[1]);\n\t\tconst l2 = Math.hypot(d2[0], d2[1]);\n\t\tif (Math.abs(denom) > 1e-12 * l1 * l2) {\n\t\t\tconst r = sub(b0, a0);\n\t\t\tconst s = cross(r, d2) / denom;\n\t\t\tconst u = cross(r, d1) / denom;\n\t\t\tconst ts = eps / l1;\n\t\t\tconst tu = eps / l2;\n\t\t\tif (s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu) {\n\t\t\t\tconst sc = Math.max(0, Math.min(1, s));\n\t\t\t\tconst uc = Math.max(0, Math.min(1, u));\n\t\t\t\tfound.push({ tp: sc, tq: uc, point: [a0[0] + d1[0] * sc, a0[1] + d1[1] * sc] });\n\t\t\t}\n\t\t}\n\t} else {\n\t\tctx.leaves = 0;\n\t\tintersectSubdivided(P.pts, 0, 1, Q.pts, 0, 1, P.pts, Q.pts, ctx, found, 0);\n\t}\n\tif (found.length === 0) return anchors.filter((a) => !a.shared);\n\n\t// Cluster hits along tangencies: consecutive events between which the pieces never\n\t// separate by more than a few epsilon are one contact.\n\tconst all = [...anchors, ...found].sort((x, y) => x.tp - y.tp || x.tq - y.tq);\n\tconst clusters: PairEvent[][] = [];\n\tfor (const ev of all) {\n\t\tconst cluster = clusters[clusters.length - 1];\n\t\tconst prev = cluster?.[cluster.length - 1];\n\t\tif (prev && (dist(prev.point, ev.point) <= eps * 2 || staysClose(P, Q, prev, ev, eps * 2))) cluster.push(ev);\n\t\telse clusters.push([ev]);\n\t}\n\tconst out: PairEvent[] = [];\n\tfor (const cluster of clusters) {\n\t\tconst shared = cluster.find((e) => e.shared);\n\t\tif (shared) continue;\n\t\tconst anchor = cluster.find((e) => e.anchor);\n\t\tout.push(anchor ?? cluster[Math.floor(cluster.length / 2)]);\n\t}\n\treturn out;\n}\n\ninterface SplitPoint {\n\tt: number;\n\tv: Vertex;\n}\n\nfunction addSplit(map: Map<Piece, SplitPoint[]>, piece: Piece, t: number, v: Vertex) {\n\tif (v === piece.a || v === piece.b) return;\n\tconst list = map.get(piece);\n\tif (list) list.push({ t, v });\n\telse map.set(piece, [{ t, v }]);\n}\n\n/** Resolves an event to a vertex, preferring the pieces' own end points. */\nfunction eventVertex(P: Piece, Q: Piece, ev: PairEvent, pool: VertexPool, eps: number): Vertex {\n\tfor (const v of [P.a, P.b, Q.a, Q.b]) if (dist(v.p, ev.point) <= eps) return v;\n\treturn pool.get(ev.point[0], ev.point[1]);\n}\n\nfunction splitPass(pieces: Piece[], pool: VertexPool, ctx: IntersectContext): { pieces: Piece[]; changed: boolean } {\n\tconst eps = ctx.eps;\n\tconst order = pieces.map((_, i) => i).sort((i, j) => pieces[i].minX - pieces[j].minX);\n\tconst splits = new Map<Piece, SplitPoint[]>();\n\tfor (let oi = 0; oi < order.length; oi++) {\n\t\tconst P = pieces[order[oi]];\n\t\tfor (let oj = oi + 1; oj < order.length; oj++) {\n\t\t\tconst Q = pieces[order[oj]];\n\t\t\tif (Q.minX > P.maxX + eps) break;\n\t\t\tif (!P.dirty && !Q.dirty) continue;\n\t\t\tif (Q.minY > P.maxY + eps || Q.maxY < P.minY - eps) continue;\n\t\t\tif (P.a === Q.a && P.b === Q.b && P.pts.length === 2 && Q.pts.length === 2) continue;\n\t\t\tif (P.a === Q.b && P.b === Q.a && P.pts.length === 2 && Q.pts.length === 2) continue;\n\t\t\tfor (const ev of pairEvents(P, Q, ctx)) {\n\t\t\t\tconst v = eventVertex(P, Q, ev, pool, eps);\n\t\t\t\taddSplit(splits, P, ev.tp, v);\n\t\t\t\taddSplit(splits, Q, ev.tq, v);\n\t\t\t}\n\t\t}\n\t}\n\tfor (const p of pieces) p.dirty = false;\n\tif (splits.size === 0) return { pieces, changed: false };\n\tconst out: Piece[] = [];\n\tfor (const piece of pieces) {\n\t\tconst list = splits.get(piece);\n\t\tif (!list) {\n\t\t\tout.push(piece);\n\t\t\tcontinue;\n\t\t}\n\t\tlist.sort((x, y) => x.t - y.t);\n\t\tlet prevT = 0;\n\t\tlet prevV = piece.a;\n\t\tconst range = piece.s1 - piece.s0;\n\t\tconst emit = (t: number, v: Vertex) => {\n\t\t\tif (v === prevV || t <= prevT) return;\n\t\t\tconst pts = subSegment(piece.pts, prevT, t);\n\t\t\tout.push(makePiece(pts, prevV, v, piece.operand, piece.src, piece.s0 + range * prevT, piece.s0 + range * t));\n\t\t\tprevT = t;\n\t\t\tprevV = v;\n\t\t};\n\t\tconst seen = new Set<Vertex>();\n\t\tfor (const { t, v } of list) {\n\t\t\tif (seen.has(v) || t <= 0 || t >= 1) continue;\n\t\t\tseen.add(v);\n\t\t\temit(t, v);\n\t\t}\n\t\tif (prevV !== piece.b) {\n\t\t\tconst pts = subSegment(piece.pts, prevT, 1);\n\t\t\tout.push(makePiece(pts, prevV, piece.b, piece.operand, piece.src, piece.s0 + range * prevT, piece.s1));\n\t\t}\n\t}\n\treturn { pieces: out, changed: true };\n}\n\n// ---------------------------------------------------------------------------\n// Edges and classification\n// ---------------------------------------------------------------------------\n\ninterface Edge {\n\ta: Vertex;\n\tb: Vertex;\n\t/** Control points from `a` to `b`. */\n\tpts: Point[];\n\tmult: number[];\n\tsrc: number;\n\t/** Source parameters at `a` and at `b`. */\n\tsa: number;\n\tsb: number;\n\tminX: number;\n\tmaxX: number;\n\tminY: number;\n\tmaxY: number;\n}\n\ninterface Arrangement {\n\tedges: Edge[];\n\tleft: number[][];\n\tright: number[][];\n\tfillRules: PolygonFillRule[];\n\tsources: Source[];\n\teps: number;\n\tscale: number;\n}\n\n/** Cuts every chain of every operand into monotone pieces and splits them where they meet. */\nfunction splitChains(\n\toperandChains: readonly (readonly (readonly BezierSegment[])[])[],\n\toptions: PathOpsOptions\n): { pieces: Piece[]; pool: VertexPool; sources: Source[]; eps: number; scale: number } {\n\tconst scale = scaleOf(operandChains);\n\tconst eps = options.epsilon && options.epsilon > 0 ? options.epsilon : 1e-9 * scale;\n\tconst pool = new VertexPool(eps);\n\tconst sources: Source[] = [];\n\tlet pieces: Piece[] = [];\n\toperandChains.forEach((chains, operand) => {\n\t\tfor (const chain of chains) {\n\t\t\tfor (const raw of chain) {\n\t\t\t\tconst straight = raw.length > 2 && isStraightSegment(raw, eps * 0.01);\n\t\t\t\tconst seg: BezierSegment = straight ? [raw[0], raw[raw.length - 1]] : raw;\n\t\t\t\tconst src = sources.length;\n\t\t\t\tsources.push({ seg, line: seg.length === 2 });\n\t\t\t\tconst ts = [0, ...monotoneParams(seg), 1];\n\t\t\t\tfor (let i = 0; i + 1 < ts.length; i++) {\n\t\t\t\t\tconst pts = subSegment(seg, ts[i], ts[i + 1]);\n\t\t\t\t\tconst a = pool.get(pts[0][0], pts[0][1]);\n\t\t\t\t\tconst b = pool.get(pts[pts.length - 1][0], pts[pts.length - 1][1]);\n\t\t\t\t\tif (a === b) continue;\n\t\t\t\t\tpieces.push(makePiece(pts, a, b, operand, src, ts[i], ts[i + 1]));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\tconst ctx: IntersectContext = { eps, leaves: 0, budget: options.subdivisionBudget ?? 20000 };\n\tlet settled = false;\n\tfor (let pass = 0; pass < 6; pass++) {\n\t\tconst result = splitPass(pieces, pool, ctx);\n\t\tpieces = result.pieces;\n\t\tif (!result.changed) {\n\t\t\tsettled = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (!settled) throw new PathOpsError('intersections did not settle');\n\treturn { pieces, pool, sources, eps, scale };\n}\n\n/** Whether two pieces between the same two vertices (`pts` in the same direction) coincide. */\nfunction samePiece(e: readonly Point[], pts: readonly Point[], eps: number): boolean {\n\tif (e.length === 2 && pts.length === 2) return true;\n\treturn segmentNearest(e, segmentPoint(pts, 0.5)).distance <= eps * 4 && segmentNearest(pts, segmentPoint(e, 0.5)).distance <= eps * 4;\n}\n\nfunction buildArrangement(inputs: PathOperandInput[], options: PathOpsOptions): Arrangement {\n\tconst operands = inputs.map(normalizeOperand);\n\tconst { pieces, sources, eps, scale } = splitChains(\n\t\toperands.map((o) => o.chains),\n\t\toptions\n\t);\n\n\t// Merge coincident pieces.\n\tconst count = operands.length;\n\tconst groups = new Map<string, Edge[]>();\n\tconst edges: Edge[] = [];\n\tfor (const piece of pieces) {\n\t\tconst forward = piece.a.id < piece.b.id;\n\t\tconst a = forward ? piece.a : piece.b;\n\t\tconst b = forward ? piece.b : piece.a;\n\t\tconst pts = forward ? piece.pts : [...piece.pts].reverse();\n\t\tconst key = `${a.id},${b.id}`;\n\t\tconst group = groups.get(key) ?? [];\n\t\tif (!groups.has(key)) groups.set(key, group);\n\t\tlet edge = group.find((e) => samePiece(e.pts, pts, eps));\n\t\tif (!edge) {\n\t\t\tedge = {\n\t\t\t\ta,\n\t\t\t\tb,\n\t\t\t\tpts,\n\t\t\t\tmult: new Array(count).fill(0),\n\t\t\t\tsrc: piece.src,\n\t\t\t\tsa: forward ? piece.s0 : piece.s1,\n\t\t\t\tsb: forward ? piece.s1 : piece.s0,\n\t\t\t\t...hull(pts)\n\t\t\t};\n\t\t\tgroup.push(edge);\n\t\t\tedges.push(edge);\n\t\t}\n\t\tedge.mult[piece.operand] += forward ? 1 : -1;\n\t}\n\tconst live = edges.filter((e) => e.mult.some((m) => m !== 0));\n\n\t// Winding numbers on both sides of every edge. Rays only meet edges whose y range (x\n\t// range for vertical rays) holds the ray, so edges are bucketed into bands on each axis.\n\tconst byY = new BandIndex(live, (e) => e.minY, (e) => e.maxY);\n\tconst byX = new BandIndex(live, (e) => e.minX, (e) => e.maxX);\n\tconst right: number[][] = [];\n\tconst left: number[][] = [];\n\tfor (let i = 0; i < live.length; i++) {\n\t\tconst edge = live[i];\n\t\tconst mid = segmentPoint(edge.pts, 0.5);\n\t\tconst tan = segmentTangent(edge.pts, 0.5);\n\t\tconst horizontal = Math.abs(tan[1]) < Math.abs(tan[0]);\n\t\tconst winding = new Array(count).fill(0);\n\t\tconst [mx, my] = mid;\n\t\tfor (const j of horizontal ? byX.at(mx) : byY.at(my)) {\n\t\t\tif (j === i) continue;\n\t\t\tconst o = live[j];\n\t\t\tlet dir = 0;\n\t\t\tif (!horizontal) {\n\t\t\t\t// Ray towards +x.\n\t\t\t\tif (o.maxX <= mx) continue;\n\t\t\t\tconst ay = o.a.y;\n\t\t\t\tconst by = o.b.y;\n\t\t\t\tif (ay <= my && by > my) dir = 1;\n\t\t\t\telse if (by <= my && ay > my) dir = -1;\n\t\t\t\telse continue;\n\t\t\t\tif (o.minX <= mx) {\n\t\t\t\t\tconst t = solveMonotone(o.pts, 1, my);\n\t\t\t\t\tif (!(segmentPoint(o.pts, t)[0] > mx)) continue;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Ray towards +y, in the frame rotated by −90° ((x, y) → (y, −x)).\n\t\t\t\tif (o.maxY <= my) continue;\n\t\t\t\tconst ax = o.a.x;\n\t\t\t\tconst bx = o.b.x;\n\t\t\t\tif (bx < mx && mx <= ax) dir = 1;\n\t\t\t\telse if (ax < mx && mx <= bx) dir = -1;\n\t\t\t\telse continue;\n\t\t\t\tif (o.minY <= my) {\n\t\t\t\t\tconst t = solveMonotone(o.pts, 0, mx);\n\t\t\t\t\tif (!(segmentPoint(o.pts, t)[1] > my)) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (let k = 0; k < count; k++) if (o.mult[k] !== 0) winding[k] += dir * o.mult[k];\n\t\t}\n\t\tconst sideIsRight = horizontal ? edge.b.x < edge.a.x : edge.b.y > edge.a.y;\n\t\tconst r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);\n\t\tright.push(r);\n\t\tleft.push(r.map((w, k) => w + edge.mult[k]));\n\t}\n\treturn { edges: live, left, right, fillRules: operands.map((o) => o.fillRule), sources, eps, scale };\n}\n\n/** Edge indices bucketed into equal bands of one coordinate. */\nclass BandIndex {\n\tprivate readonly bands: number[][];\n\tprivate readonly min: number;\n\tprivate readonly size: number;\n\tconstructor(edges: Edge[], lo: (e: Edge) => number, hi: (e: Edge) => number) {\n\t\tlet min = Infinity;\n\t\tlet max = -Infinity;\n\t\tfor (const e of edges) {\n\t\t\tmin = Math.min(min, lo(e));\n\t\t\tmax = Math.max(max, hi(e));\n\t\t}\n\t\tconst count = Math.max(1, Math.min(4096, Math.ceil(Math.sqrt(edges.length) * 2)));\n\t\tthis.min = min;\n\t\tthis.size = max > min ? (max - min) / count : 1;\n\t\tthis.bands = Array.from({ length: count }, () => []);\n\t\tedges.forEach((e, i) => {\n\t\t\tconst from = this.index(lo(e));\n\t\t\tconst to = this.index(hi(e));\n\t\t\tfor (let b = from; b <= to; b++) this.bands[b].push(i);\n\t\t});\n\t}\n\tprivate index(v: number): number {\n\t\tconst k = Math.floor((v - this.min) / this.size);\n\t\treturn Math.max(0, Math.min(this.bands.length - 1, Number.isFinite(k) ? k : 0));\n\t}\n\tat(v: number): readonly number[] {\n\t\treturn this.bands[this.index(v)] ?? [];\n\t}\n}\n\nfunction insideFlags(windings: number[], fillRules: PolygonFillRule[]): boolean[] {\n\treturn windings.map((w, k) => (fillRules[k] === 'evenodd' ? (w & 1) === 1 : w !== 0));\n}\n\n// ---------------------------------------------------------------------------\n// Tracing and reconstruction\n// ---------------------------------------------------------------------------\n\ninterface DirectedEdge {\n\tfrom: Vertex;\n\tto: Vertex;\n\tpts: Point[];\n\tsrc: number;\n\tsFrom: number;\n\tsTo: number;\n\toutAngle: number;\n\tbackAngle: number;\n\tlength: number;\n}\n\nconst TWO_PI = Math.PI * 2;\nconst TIE = 1e-3;\n\nfunction directed(edge: Edge, forward: boolean): DirectedEdge {\n\tconst pts = forward ? edge.pts : [...edge.pts].reverse();\n\tconst out = segmentTangent(pts, 0);\n\tconst end = segmentTangent(pts, 1);\n\treturn {\n\t\tfrom: forward ? edge.a : edge.b,\n\t\tto: forward ? edge.b : edge.a,\n\t\tpts,\n\t\tsrc: edge.src,\n\t\tsFrom: forward ? edge.sa : edge.sb,\n\t\tsTo: forward ? edge.sb : edge.sa,\n\t\toutAngle: Math.atan2(out[1], out[0]),\n\t\tbackAngle: Math.atan2(-end[1], -end[0]),\n\t\tlength: dist(pts[0], pts[pts.length - 1]) + (pts.length > 2 ? flatness(pts) : 0)\n\t};\n}\n\n/** Angle of the chord from the start of `pts` to the point at fraction `t` (by parameter). */\nfunction chordAngle(pts: readonly Point[], t: number): number {\n\tconst p = segmentPoint(pts, t);\n\treturn Math.atan2(p[1] - pts[0][1], p[0] - pts[0][0]);\n}\n\nfunction mod2pi(a: number): number {\n\tconst r = a - Math.floor(a / TWO_PI) * TWO_PI;\n\treturn r >= TWO_PI ? 0 : r;\n}\n\n/** Traces the kept directed edges (result on the left) into rings. */\nfunction traceRings(edges: DirectedEdge[], scale: number): DirectedEdge[][] {\n\tconst outgoing = new Map<number, DirectedEdge[]>();\n\tfor (const e of edges) {\n\t\tconst list = outgoing.get(e.from.id);\n\t\tif (list) list.push(e);\n\t\telse outgoing.set(e.from.id, [e]);\n\t}\n\tconst next = (incoming: DirectedEdge): DirectedEdge | undefined => {\n\t\tconst candidates = outgoing.get(incoming.to.id)?.filter((c) => !used.has(c) || c === start);\n\t\tif (!candidates || candidates.length === 0) return undefined;\n\t\tif (candidates.length === 1) return candidates[0];\n\t\tconst turns = candidates.map((c) => mod2pi(incoming.backAngle - c.outAngle));\n\t\tconst tied = (x: number) => x < TIE || x > TWO_PI - TIE;\n\t\tlet ambiguous = turns.some(tied);\n\t\tif (!ambiguous) {\n\t\t\tconst sorted = [...turns].sort((a, b) => a - b);\n\t\t\tfor (let i = 1; i < sorted.length; i++) if (sorted[i] - sorted[i - 1] < TIE) ambiguous = true;\n\t\t}\n\t\tif (ambiguous) {\n\t\t\t// Tangent directions tie (tangent curves): compare chords a short way along each\n\t\t\t// curve instead, which separates curves by their curvature.\n\t\t\tconst back = [...incoming.pts].reverse();\n\t\t\tlet delta = Math.min(incoming.length, ...candidates.map((c) => c.length));\n\t\t\tdelta = Math.min(delta * 0.25, scale * 0.02);\n\t\t\tconst frac = (e: DirectedEdge) => Math.min(1, Math.max(1e-6, delta / Math.max(e.length, 1e-300)));\n\t\t\tconst backChord = chordAngle(back, frac(incoming));\n\t\t\tfor (let i = 0; i < candidates.length; i++) {\n\t\t\t\tturns[i] = mod2pi(backChord - chordAngle(candidates[i].pts, frac(candidates[i])));\n\t\t\t\tif (turns[i] < 1e-12) turns[i] = TWO_PI;\n\t\t\t}\n\t\t} else {\n\t\t\tfor (let i = 0; i < turns.length; i++) if (turns[i] < 1e-15) turns[i] = TWO_PI;\n\t\t}\n\t\tlet best = 0;\n\t\tfor (let i = 1; i < candidates.length; i++) if (turns[i] < turns[best]) best = i;\n\t\treturn candidates[best];\n\t};\n\n\tconst used = new Set<DirectedEdge>();\n\tconst rings: DirectedEdge[][] = [];\n\tlet start: DirectedEdge | null = null;\n\tfor (const first of edges) {\n\t\tif (used.has(first)) continue;\n\t\tstart = first;\n\t\tconst ring: DirectedEdge[] = [];\n\t\tlet edge: DirectedEdge | undefined = first;\n\t\tlet guard = edges.length + 1;\n\t\tlet closed = false;\n\t\twhile (edge && guard-- > 0) {\n\t\t\tused.add(edge);\n\t\t\tring.push(edge);\n\t\t\tedge = next(edge);\n\t\t\tif (edge === first) {\n\t\t\t\tclosed = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!closed) throw new PathOpsError('result outline does not close');\n\t\trings.push(ring);\n\t}\n\treturn rings;\n}\n\nfunction mergeable(a: DirectedEdge, b: DirectedEdge, sources: Source[], eps: number): boolean {\n\tif (a.src === b.src && Math.abs(a.sTo - b.sFrom) < 1e-9 && a.sTo - a.sFrom > 0 === b.sTo - b.sFrom > 0) return true;\n\tif (a.pts.length === 2 && b.pts.length === 2) {\n\t\t// Collinear lines from different sources (e.g. shared rectangle sides).\n\t\tconst p = a.pts[0];\n\t\tconst q = b.pts[1];\n\t\tconst m = a.pts[1];\n\t\tconst chord = sub(q, p);\n\t\tconst len = Math.hypot(chord[0], chord[1]);\n\t\tif (len === 0) return false;\n\t\tconst off = Math.abs(cross(chord, sub(m, p))) / len;\n\t\tconst along = dot(chord, sub(m, p));\n\t\treturn off <= eps && along > 0 && along < len * len;\n\t}\n\tvoid sources;\n\treturn false;\n}\n\n/** Rebuilds a ring as segments: pieces of one source become one exact sub-curve. */\nfunction rebuildRing(ring: DirectedEdge[], sources: Source[], eps: number): BezierSegment[] {\n\tconst n = ring.length;\n\tlet startIndex = 0;\n\tfor (let k = 0; k < n; k++) {\n\t\tif (!mergeable(ring[(k + n - 1) % n], ring[k], sources, eps)) {\n\t\t\tstartIndex = k;\n\t\t\tbreak;\n\t\t}\n\t}\n\tconst ordered = [...ring.slice(startIndex), ...ring.slice(0, startIndex)];\n\tconst out: BezierSegment[] = [];\n\tlet i = 0;\n\twhile (i < ordered.length) {\n\t\tconst first = ordered[i];\n\t\tlet j = i + 1;\n\t\twhile (j < ordered.length && mergeable(ordered[j - 1], ordered[j], sources, eps)) j++;\n\t\tconst last = ordered[j - 1];\n\t\tif (j - i === 1) {\n\t\t\tout.push(first.pts);\n\t\t} else if (first.pts.length === 2 && ordered.slice(i, j).every((e) => e.pts.length === 2)) {\n\t\t\tout.push([first.from.p, last.to.p]);\n\t\t} else {\n\t\t\tconst source = sources[first.src];\n\t\t\tout.push(snapEnds(subSegment(source.seg, first.sFrom, last.sTo), first.from.p, last.to.p));\n\t\t}\n\t\ti = j;\n\t}\n\treturn out;\n}\n\ninterface Ring {\n\tsegs: BezierSegment[];\n\tarea: number;\n}\n\nfunction extractRings(arr: Arrangement, isInside: (flags: boolean[]) => boolean): Ring[] {\n\tconst kept: DirectedEdge[] = [];\n\tarr.edges.forEach((edge, i) => {\n\t\tconst l = isInside(insideFlags(arr.left[i], arr.fillRules));\n\t\tconst r = isInside(insideFlags(arr.right[i], arr.fillRules));\n\t\tif (l === r) return;\n\t\tkept.push(directed(edge, l));\n\t});\n\tconst rings: Ring[] = [];\n\tconst sliver = Math.max(arr.eps * 10, 1e-7 * arr.scale);\n\tfor (const ring of traceRings(kept, arr.scale)) {\n\t\tconst segs = rebuildRing(ring, arr.sources, arr.eps);\n\t\tconst area = contourSignedArea(segs);\n\t\tlet perimeter = 0;\n\t\tfor (const s of segs) perimeter += s.length === 2 ? dist(s[0], s[1]) : segmentLength(s, 0, 1, 1e-6);\n\t\tif (!(perimeter > 0) || (2 * Math.abs(area)) / perimeter < sliver) continue;\n\t\trings.push({ segs, area });\n\t}\n\treturn rings;\n}\n\n/** A point inside the region just left of a ring (for hole assignment). */\nfunction ringSample(ring: Ring): Point {\n\t// The midpoint of the longest segment: never a vertex shared with another ring.\n\tlet best = ring.segs[0];\n\tlet bestLen = -1;\n\tfor (const s of ring.segs) {\n\t\tconst l = dist(s[0], s[s.length - 1]);\n\t\tif (l > bestLen) {\n\t\t\tbest = s;\n\t\t\tbestLen = l;\n\t\t}\n\t}\n\treturn segmentPoint(best, 0.5);\n}\n\n/** Groups rings into outer rings with their holes (counter-clockwise outers in a y-up frame). */\nfunction groupRings(rings: Ring[]): Ring[][] {\n\tconst outers = rings.filter((r) => r.area > 0).sort((a, b) => b.area - a.area);\n\tconst holes = rings.filter((r) => r.area < 0);\n\tconst groups: Ring[][] = outers.map((o) => [o]);\n\tfor (const hole of holes) {\n\t\tconst sample = ringSample(hole);\n\t\tfor (let i = outers.length - 1; i >= 0; i--) {\n\t\t\tif (outers[i].area >= -hole.area && windingNumber([outers[i].segs], sample) !== 0) {\n\t\t\t\tgroups[i].push(hole);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn groups;\n}\n\nfunction ringsToPath(rings: Ring[]): BezierPath {\n\treturn contoursToPath(rings.map((r) => ({ segments: r.segs, closed: true })));\n}\n\nfunction predicate(operation: PathBooleanOperation): (flags: boolean[]) => boolean {\n\tswitch (operation) {\n\t\tcase 'union':\n\t\t\treturn (f) => f.some(Boolean);\n\t\tcase 'intersection':\n\t\t\treturn (f) => f.length > 0 && f.every(Boolean);\n\t\tcase 'difference':\n\t\t\treturn (f) => f[0] === true && !f.slice(1).some(Boolean);\n\t\tcase 'xor':\n\t\t\treturn (f) => f.filter(Boolean).length % 2 === 1;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Polygon fallback\n// ---------------------------------------------------------------------------\n\nfunction flattenOperand(input: PathOperandInput, tolerance: number) {\n\tconst path = isOperandObject(input) ? input.path : input;\n\tconst fillRule = isOperandObject(input) ? input.fillRule ?? 'nonzero' : 'nonzero';\n\tconst rings = pathToContours(path ?? '')\n\t\t.map((c) => flattenChain(closeContour(c, true), tolerance))\n\t\t.filter((r) => r.length >= 3 && r.every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1])));\n\treturn { rings, fillRule };\n}\n\nfunction multiToPath(multi: MultiPolygon, tolerance: number): BezierPath {\n\tif (multi.length === 0) return [];\n\treturn toBezierPath(polygonToSvgPath(multi, { curves: 'fit', curveTolerance: tolerance, precision: 9 }));\n}\n\nfunction reasonOf(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\n// ---------------------------------------------------------------------------\n// Public operations\n// ---------------------------------------------------------------------------\n\n/**\n * Applies a boolean operation to any number of path operands, keeping curves:\n * - `union`: covered by any operand.\n * - `intersection`: covered by every operand.\n * - `difference`: the first operand minus all others.\n * - `xor`: covered by an odd number of operands (Exclude).\n *\n * Open subpaths are closed for filling. Non-finite input gives an empty path (`error`); with\n * `fallback: false` a failure of the curve engine throws a `PathOpsError`. The result is a set of simple closed subpaths that\n * never cross (they may touch at points): outer subpaths run counter-clockwise in a y-up\n * frame (clockwise on screen), holes the other way, so it fills the same with `nonzero`\n * and `evenodd`. Segments of the inputs are kept as exact sub-curves.\n */\nexport function pathBoolean(\n\toperation: PathBooleanOperation,\n\toperands: PathOperandInput[],\n\toptions: PathOpsOptions = {}\n): PathBooleanResult {\n\tif (operands.length === 0) return { path: [], engine: 'curve' };\n\ttry {\n\t\tconst arr = buildArrangement(operands, options);\n\t\treturn { path: ringsToPath(extractRings(arr, predicate(operation))), engine: 'curve' };\n\t} catch (error) {\n\t\tconst reason = reasonOf(error);\n\t\tif (reason === NON_FINITE) return { path: [], engine: 'curve', error: reason };\n\t\tif (options.fallback === false) throw error;\n\t\toptions.onFallback?.(reason);\n\t\tconst tolerance = options.fallbackTolerance ?? 0.05;\n\t\tconst multi = polygonBoolean(\n\t\t\toperation,\n\t\t\toperands.map((o) => flattenOperand(o, tolerance)),\n\t\t\t{ epsilon: options.epsilon }\n\t\t);\n\t\treturn { path: multiToPath(multi, tolerance), engine: 'polygon', fallbackReason: reason };\n\t}\n}\n\nexport const unitePaths = (a: PathOperandInput, b: PathOperandInput, options?: PathOpsOptions) =>\n\tpathBoolean('union', [a, b], options).path;\nexport const intersectPaths = (a: PathOperandInput, b: PathOperandInput, options?: PathOpsOptions) =>\n\tpathBoolean('intersection', [a, b], options).path;\nexport const subtractPaths = (a: PathOperandInput, b: PathOperandInput, options?: PathOpsOptions) =>\n\tpathBoolean('difference', [a, b], options).path;\nexport const excludePaths = (a: PathOperandInput, b: PathOperandInput, options?: PathOpsOptions) =>\n\tpathBoolean('xor', [a, b], options).path;\n\n/**\n * Splits the arrangement of the operands into its regions (Divide): every connected area\n * covered by the same set of operands becomes one piece (an outer subpath with its holes).\n * Pieces are ordered by operand set, then by area (largest first).\n */\nexport function dividePaths(operands: PathOperandInput[], options: PathOpsOptions = {}): DividePathsResult {\n\tif (operands.length === 0) return { pieces: [], engine: 'curve' };\n\ttry {\n\t\tconst arr = buildArrangement(operands, options);\n\t\tconst signatures = new Map<string, number[]>();\n\t\tconst note = (windings: number[]) => {\n\t\t\tconst flags = insideFlags(windings, arr.fillRules);\n\t\t\tif (!flags.some(Boolean)) return;\n\t\t\tconst key = flags.map((f) => (f ? '1' : '0')).join('');\n\t\t\tif (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => (f ? [k] : [])));\n\t\t};\n\t\tarr.edges.forEach((_, i) => {\n\t\t\tnote(arr.left[i]);\n\t\t\tnote(arr.right[i]);\n\t\t});\n\t\tconst pieces: PathPiece[] = [];\n\t\tfor (const key of [...signatures.keys()].sort()) {\n\t\t\tconst rings = extractRings(arr, (flags) => flags.map((f) => (f ? '1' : '0')).join('') === key);\n\t\t\tconst groups = groupRings(rings).sort((a, b) => b[0].area - a[0].area);\n\t\t\tfor (const group of groups) pieces.push({ path: ringsToPath(group), operands: signatures.get(key)! });\n\t\t}\n\t\treturn { pieces, engine: 'curve' };\n\t} catch (error) {\n\t\tconst reason = reasonOf(error);\n\t\tif (reason === NON_FINITE) return { pieces: [], engine: 'curve', error: reason };\n\t\tif (options.fallback === false) throw error;\n\t\toptions.onFallback?.(reason);\n\t\tconst tolerance = options.fallbackTolerance ?? 0.05;\n\t\tconst pieces = dividePolygons(\n\t\t\toperands.map((o) => flattenOperand(o, tolerance)),\n\t\t\t{ epsilon: options.epsilon }\n\t\t).map((p) => ({ path: multiToPath([p.polygon], tolerance), operands: p.operands }));\n\t\treturn { pieces, engine: 'polygon', fallbackReason: reason };\n\t}\n}\n\n/**\n * Resolves self-intersections and overlaps of one filled path into simple, consistently\n * oriented subpaths (outer subpaths and holes) under its fill rule.\n */\nexport function normalizeBezierPath(\n\tpath: BezierPathInput,\n\tfillRule: PolygonFillRule = 'nonzero',\n\toptions: PathOpsOptions = {}\n): BezierPath {\n\treturn pathBoolean('union', [{ path, fillRule }], options).path;\n}\n\n/** Filled area of a path under its fill rule (overlaps counted once; exact for curves). */\nexport function bezierPathArea(path: BezierPathInput, fillRule: PolygonFillRule = 'nonzero', options: PathOpsOptions = {}): number {\n\tconst normalized = normalizeBezierPath(path, fillRule, options);\n\tlet sum = 0;\n\tfor (const contour of pathToContours(normalized)) sum += contourSignedArea(closeContour(contour, true));\n\treturn Math.abs(sum);\n}\n\n/**\n * Normalised rings of a filled path with their nesting: used by offset / outline stroke to\n * drop spurious contours. `parent` is the index of the smallest enclosing ring.\n */\nexport interface NestedRing {\n\tsegments: BezierSegment[];\n\tarea: number;\n\tparent: number;\n}\n\n/** Internal: rings of a boolean result with their containment tree. */\nexport function pathBooleanRings(\n\toperation: PathBooleanOperation | ((flags: boolean[]) => boolean),\n\toperands: PathOperandInput[],\n\toptions: PathOpsOptions = {}\n): NestedRing[] {\n\tconst arr = buildArrangement(operands, options);\n\tconst rings = extractRings(arr, typeof operation === 'function' ? operation : predicate(operation));\n\tconst order = rings.map((_, i) => i).sort((a, b) => Math.abs(rings[a].area) - Math.abs(rings[b].area));\n\treturn rings.map((ring, i) => {\n\t\tconst sample = ringSample(ring);\n\t\tlet parent = -1;\n\t\tfor (const j of order) {\n\t\t\tif (j === i || Math.abs(rings[j].area) <= Math.abs(ring.area)) continue;\n\t\t\tif (windingNumber([rings[j].segs], sample) !== 0) {\n\t\t\t\tparent = j;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn { segments: ring.segs, area: ring.area, parent };\n\t});\n}\n\n/** Contours of nested rings, keeping the ones `keep` accepts. */\nexport function nestedRingsToPath(rings: NestedRing[], keep: (index: number) => boolean = () => true): BezierPath {\n\tconst contours: BezierContour[] = [];\n\trings.forEach((r, i) => {\n\t\tif (keep(i)) contours.push({ segments: r.segments, closed: true });\n\t});\n\treturn contoursToPath(contours);\n}\n\n// ---------------------------------------------------------------------------\n// Planar split (for planar maps: Shape Builder, fill regions, Pathfinder Outline)\n// ---------------------------------------------------------------------------\n\n/** One edge of `planarSplit`: a piece of an input segment between two vertices. */\nexport interface PlanarSplitEdge {\n\t/** Vertex indices at the start and end of `pts`. */\n\ta: number;\n\tb: number;\n\t/** Control points from vertex `a` to vertex `b`. */\n\tpts: Point[];\n\t/** Inputs whose chains run along this edge, ascending (several where outlines coincide). */\n\toperands: number[];\n\t/** The source segment it is a piece of (index into `sources`) and its parameters there at `a` and `b`. */\n\tsrc: number;\n\tsa: number;\n\tsb: number;\n}\n\nexport interface PlanarSplitResult {\n\tvertices: Point[];\n\tedges: PlanarSplitEdge[];\n\t/** The input segments, as `src` refers to them. */\n\tsources: BezierSegment[];\n\teps: number;\n\tscale: number;\n}\n\n/**\n * The chains of every input (open or closed, as given: nothing is closed for filling) split\n * wherever they cross or touch each other or themselves, with the same intersection search as\n * the boolean operations. Coincident pieces become one edge that lists every input along it.\n * Throws a `PathOpsError` when the search does not settle or the input is not finite.\n */\nexport function planarSplit(inputs: readonly (readonly (readonly BezierSegment[])[])[], options: PathOpsOptions = {}): PlanarSplitResult {\n\tfor (const chains of inputs) {\n\t\tfor (const chain of chains) {\n\t\t\tfor (const seg of chain) for (const p of seg) if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) throw new PathOpsError(NON_FINITE);\n\t\t}\n\t}\n\tconst { pieces, pool, sources, eps, scale } = splitChains(inputs, options);\n\tconst groups = new Map<string, PlanarSplitEdge[]>();\n\tconst edges: PlanarSplitEdge[] = [];\n\tfor (const piece of pieces) {\n\t\tconst forward = piece.a.id < piece.b.id;\n\t\tconst a = forward ? piece.a : piece.b;\n\t\tconst b = forward ? piece.b : piece.a;\n\t\tconst pts = forward ? piece.pts : [...piece.pts].reverse();\n\t\tconst key = `${a.id},${b.id}`;\n\t\tlet group = groups.get(key);\n\t\tif (!group) groups.set(key, (group = []));\n\t\tconst edge = group.find((e) => samePiece(e.pts, pts, eps));\n\t\tif (edge) {\n\t\t\tif (!edge.operands.includes(piece.operand)) edge.operands = [...edge.operands, piece.operand].sort((x, y) => x - y);\n\t\t\tcontinue;\n\t\t}\n\t\tconst created: PlanarSplitEdge = { 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 };\n\t\tgroup.push(created);\n\t\tedges.push(created);\n\t}\n\treturn { vertices: pool.vertices.map((v) => v.p), edges, sources: sources.map((s) => s.seg), eps, scale };\n}\n\n/** Result of `pathBooleanSelect`: per selector, its connected parts (each an outer subpath with its holes). */\nexport interface PathSelectResult extends PathOpsReport {\n\tparts: BezierPath[][];\n}\n\n/**\n * Several boolean selections over one arrangement of the operands (the Pathfinder panel's\n * Trim, Merge, Crop and Minus Back are each a selection per output). A selector gets the\n * ascending indices of the operands covering an area and keeps the area when it returns true.\n * Each selection comes back as its connected parts, largest first, built as `pathBoolean`\n * builds its result; areas that only touch at an edge are one part. Falls back to the\n * polygon engine like `pathBoolean`.\n */\nexport function pathBooleanSelect(\n\toperands: PathOperandInput[],\n\tselectors: readonly ((covering: readonly number[]) => boolean)[],\n\toptions: PathOpsOptions = {}\n): PathSelectResult {\n\tif (operands.length === 0) return { parts: selectors.map(() => []), engine: 'curve' };\n\tconst covering = (flags: boolean[]) => flags.flatMap((f, k) => (f ? [k] : []));\n\ttry {\n\t\tconst arr = buildArrangement(operands, options);\n\t\tconst parts = selectors.map((select) => {\n\t\t\tconst rings = extractRings(arr, (flags) => select(covering(flags)));\n\t\t\treturn groupRings(rings)\n\t\t\t\t.sort((a, b) => b[0].area - a[0].area)\n\t\t\t\t.map(ringsToPath);\n\t\t});\n\t\treturn { parts, engine: 'curve' };\n\t} catch (error) {\n\t\tconst reason = reasonOf(error);\n\t\tif (reason === NON_FINITE) return { parts: selectors.map(() => []), engine: 'curve', error: reason };\n\t\tif (options.fallback === false) throw error;\n\t\toptions.onFallback?.(reason);\n\t\tconst tolerance = options.fallbackTolerance ?? 0.05;\n\t\tconst pieces = dividePolygons(\n\t\t\toperands.map((o) => flattenOperand(o, tolerance)),\n\t\t\t{ epsilon: options.epsilon }\n\t\t);\n\t\tconst parts = selectors.map((select) => {\n\t\t\tconst picked = pieces.filter((p) => select(p.operands));\n\t\t\tif (!picked.length) return [];\n\t\t\tconst multi = polygonBoolean('union', [picked.map((p) => p.polygon)], { epsilon: options.epsilon });\n\t\t\treturn multi.map((polygon) => multiToPath([polygon], tolerance));\n\t\t});\n\t\treturn { parts, engine: 'polygon', fallbackReason: reason };\n\t}\n}\n","/**\n * Offset Path and Outline Stroke on Bézier paths.\n *\n * The stroker offsets every segment by ± half the width (lines exactly, curves by cubic\n * approximations refined until they stay within `tolerance` of the true offset), joins the\n * pieces (miter with a miter limit, round or bevel on the outer side of a turn; through the\n * vertex on the inner side) and adds caps (butt, round, square) to open subpaths. Dashes\n * follow arc length, restart on every subpath and join across the start of a closed\n * subpath. The raw outline overlaps itself, so it is filled with the non-zero rule and\n * normalised with the curve boolean engine.\n *\n * When the stroke is wider than a curve's radius of curvature the inner offset turns inside\n * out and its loops cancel winding, leaving holes that are really covered. Every face of\n * the true result is bounded by the arrangement, so a face is spurious exactly when a point\n * deep inside it is closer to the path than the offset distance (holes when growing,\n * islands when insetting); such faces are dropped.\n *\n * Portions ported from vectorcraft-main/crates/pathops/src/offset.rs,\n * vectorcraft-main/crates/pathops/src/boolean.rs (`try_merge`) and\n * vectorcraft-main/crates/effects/src/stroke/dash.rs (MIT OR Apache-2.0, © ArtCraft Team\n * and contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from '../types.js';\nimport type { PolygonFillRule } from '../polygonBoolean.js';\nimport { fitCubicRun } from '../curveFit.js';\nimport {\n\tadd,\n\tcloseContour,\n\tcontoursToPath,\n\tcross,\n\tdist,\n\tdistanceToBezierPath,\n\tdot,\n\tflattenChain,\n\tpathToContours,\n\tsamePoint,\n\tscale,\n\tsegmentCurvature,\n\tsegmentLength,\n\tsegmentNearest,\n\tsegmentPoint,\n\tsegmentTangent,\n\tsplitSegment,\n\tsub,\n\ttrimSegments,\n\ttype BezierContour,\n\ttype BezierPath,\n\ttype BezierPathInput,\n\ttype BezierSegment\n} from './bezier.js';\nimport {\n\tnestedRingsToPath,\n\tpathBoolean,\n\tpathBooleanRings,\n\ttype NestedRing,\n\ttype PathOperandInput,\n\ttype PathOpsOptions\n} from './boolean.js';\n\n/** Line join (as SVG `stroke-linejoin`). */\nexport type StrokeJoin = 'miter' | 'round' | 'bevel';\n/** Line cap (as SVG `stroke-linecap`). */\nexport type StrokeCap = 'butt' | 'round' | 'square';\n\nexport interface OutlineStrokeOptions extends PathOpsOptions {\n\t/** Stroke width. Non-positive or non-finite widths give an empty result. */\n\twidth: number;\n\t/** Default `miter` (as SVG). */\n\tjoin?: StrokeJoin;\n\t/** Default `butt` (as SVG). */\n\tcap?: StrokeCap;\n\t/** Miter length / stroke width above which a miter join is bevelled (default 4, as SVG). */\n\tmiterLimit?: number;\n\t/** Dash pattern (as SVG `stroke-dasharray`); odd patterns repeat twice. */\n\tdash?: readonly number[];\n\t/** Distance into the dash pattern at the start of each subpath (as `stroke-dashoffset`). */\n\tdashOffset?: number;\n\t/** Maximum distance between the approximated and the true offset curve (default 0.01). */\n\ttolerance?: number;\n}\n\nexport interface OffsetPathOptions extends PathOpsOptions {\n\t/** Corner style of the grown / shrunk outline (default `miter`). */\n\tjoin?: StrokeJoin;\n\t/** Miter limit (default 4). */\n\tmiterLimit?: number;\n\t/** Fill rule of the input (default `nonzero`). */\n\tfillRule?: PolygonFillRule;\n\t/** Maximum distance between the approximated and the true offset curve (default 0.01). */\n\ttolerance?: number;\n}\n\nconst DEFAULT_TOLERANCE = 0.01;\n\nconst normal = (t: Point): Point => [-t[1], t[0]];\n\nfunction raise(seg: BezierSegment): Point[] {\n\tif (seg.length !== 3) return [...seg];\n\tconst [p0, p1, p2] = seg;\n\treturn [p0, [p0[0] + (2 / 3) * (p1[0] - p0[0]), p0[1] + (2 / 3) * (p1[1] - p0[1])], [p2[0] + (2 / 3) * (p1[0] - p2[0]), p2[1] + (2 / 3) * (p1[1] - p2[1])], p2];\n}\n\nfunction isDegenerate(seg: BezierSegment, tol: number): boolean {\n\treturn seg.every((p) => dist(p, seg[0]) <= tol);\n}\n\n// ---------------------------------------------------------------------------\n// Offsetting segments\n// ---------------------------------------------------------------------------\n\n/** Appends cubic / line approximations of the curve offset by `d` (left in a y-up frame). */\nfunction offsetSegment(seg: BezierSegment, d: number, tol: number, out: Point[][], depth = 0) {\n\tif (seg.length === 2) {\n\t\tconst n = normal(segmentTangent(seg, 0));\n\t\tout.push([add(seg[0], scale(n, d)), add(seg[1], scale(n, d))]);\n\t\treturn;\n\t}\n\tconst c = raise(seg);\n\tconst t0 = segmentTangent(c, 0);\n\tconst t1 = segmentTangent(c, 1);\n\tconst q0 = add(c[0], scale(normal(t0), d));\n\tconst q3 = add(c[3], scale(normal(t1), d));\n\tconst k0 = segmentCurvature(c, 0);\n\tconst k1 = segmentCurvature(c, 1);\n\tconst f0 = Number.isFinite(k0) ? 1 - d * k0 : 1;\n\tconst f1 = Number.isFinite(k1) ? 1 - d * k1 : 1;\n\tconst q1 = add(q0, scale(sub(c[1], c[0]), f0));\n\tconst q2 = add(q3, scale(sub(c[2], c[3]), f1));\n\tconst approx: Point[] = [q0, q1, q2, q3];\n\tif (depth < 10) {\n\t\tlet err = 0;\n\t\tfor (const t of [0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875]) {\n\t\t\tconst target = add(segmentPoint(c, t), scale(normal(segmentTangent(c, t)), d));\n\t\t\terr = Math.max(err, segmentNearest(approx, target).distance);\n\t\t\tif (err > tol) break;\n\t\t}\n\t\t// The approximation must not wander off either: check its midpoint against the curve.\n\t\tif (err <= tol) {\n\t\t\tconst m = segmentPoint(approx, 0.5);\n\t\t\tconst back = segmentNearest(c, m).distance;\n\t\t\terr = Math.max(err, Math.abs(back - Math.abs(d)));\n\t\t}\n\t\tif (err > tol) {\n\t\t\tconst [a, b] = splitSegment(c, 0.5);\n\t\t\toffsetSegment(a, d, tol, out, depth + 1);\n\t\t\toffsetSegment(b, d, tol, out, depth + 1);\n\t\t\treturn;\n\t\t}\n\t}\n\tout.push(approx);\n}\n\n/** Appends a circular arc (center, radius, start angle, sweep) as cubics, at most 90° each. */\nfunction pushArc(out: Point[][], center: Point, radius: number, start: number, sweep: number, from: Point, to: Point) {\n\tconst n = Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2) - 1e-9));\n\tconst step = sweep / n;\n\tconst k = (4 / 3) * Math.tan(step / 4);\n\tlet prev = from;\n\tfor (let i = 0; i < n; i++) {\n\t\tconst a0 = start + step * i;\n\t\tconst a1 = a0 + step;\n\t\tconst c0 = Math.cos(a0);\n\t\tconst s0 = Math.sin(a0);\n\t\tconst c1 = Math.cos(a1);\n\t\tconst s1 = Math.sin(a1);\n\t\tconst end: Point = i === n - 1 ? to : [center[0] + radius * c1, center[1] + radius * s1];\n\t\tout.push([\n\t\t\tprev,\n\t\t\t[center[0] + radius * (c0 - k * s0), center[1] + radius * (s0 + k * c0)],\n\t\t\t[center[0] + radius * (c1 + k * s1), center[1] + radius * (s1 - k * c1)],\n\t\t\tend\n\t\t]);\n\t\tprev = end;\n\t}\n}\n\nfunction lineTo(out: Point[][], from: Point, to: Point) {\n\tif (!samePoint(from, to)) out.push([from, to]);\n}\n\ninterface JoinStyle {\n\tjoin: StrokeJoin;\n\tmiterLimit: number;\n}\n\n/** Joins the offset at vertex `p` from the incoming tangent `tin` to the outgoing `tout`. */\nfunction addJoin(out: Point[][], p: Point, tin: Point, tout: Point, d: number, style: JoinStyle) {\n\tconst from = add(p, scale(normal(tin), d));\n\tconst to = add(p, scale(normal(tout), d));\n\tconst turn = cross(tin, tout);\n\tconst cos = dot(tin, tout);\n\tif (Math.abs(turn) < 1e-9 && cos > 0) {\n\t\tlineTo(out, from, to);\n\t\treturn;\n\t}\n\tconst reversal = Math.abs(turn) < 1e-9 && cos < 0;\n\tconst inner = !reversal && turn > 0 === d > 0;\n\tif (inner) {\n\t\tlineTo(out, from, p);\n\t\tlineTo(out, p, to);\n\t\treturn;\n\t}\n\tconst theta = reversal ? (d > 0 ? -Math.PI : Math.PI) : Math.atan2(turn, cos);\n\tif (style.join === 'round') {\n\t\tconst v = sub(from, p);\n\t\tpushArc(out, p, Math.abs(d), Math.atan2(v[1], v[0]), theta, from, to);\n\t\treturn;\n\t}\n\tif (style.join === 'miter' && !reversal) {\n\t\t// Miter length / width = 1 / sin(φ / 2), φ the angle between the segments.\n\t\tconst phi = Math.PI - Math.abs(theta);\n\t\tconst ratio = 1 / Math.max(Math.sin(phi / 2), 1e-12);\n\t\tif (ratio <= style.miterLimit) {\n\t\t\tconst nin = normal(tin);\n\t\t\tconst nout = normal(tout);\n\t\t\tconst s = d / (1 + dot(nin, nout));\n\t\t\tconst miter = add(p, scale(add(nin, nout), s));\n\t\t\tlineTo(out, from, miter);\n\t\t\tlineTo(out, miter, to);\n\t\t\treturn;\n\t\t}\n\t}\n\tlineTo(out, from, to);\n}\n\n/** One side of a segment chain offset by `d`, with joins; closed chains join their ends too. */\nfunction offsetChain(segs: BezierSegment[], d: number, closed: boolean, style: JoinStyle, tol: number): Point[][] {\n\tconst out: Point[][] = [];\n\tconst pen = () => (out.length > 0 ? out[out.length - 1][out[out.length - 1].length - 1] : null);\n\tfor (let i = 0; i < segs.length; i++) {\n\t\tif (i > 0) addJoin(out, segs[i][0], segmentTangent(segs[i - 1], 1), segmentTangent(segs[i], 0), d, style);\n\t\tconst pieces: Point[][] = [];\n\t\toffsetSegment(segs[i], d, tol, pieces);\n\t\tfor (const piece of pieces) {\n\t\t\tconst at = pen();\n\t\t\tif (at && !samePoint(at, piece[0])) lineTo(out, at, piece[0]);\n\t\t\tout.push(piece);\n\t\t}\n\t}\n\tif (closed && segs.length > 0) {\n\t\tconst last = segs[segs.length - 1];\n\t\taddJoin(out, segs[0][0], segmentTangent(last, 1), segmentTangent(segs[0], 0), d, style);\n\t\tconst at = pen();\n\t\tif (at && out.length > 0 && !samePoint(at, out[0][0])) lineTo(out, at, out[0][0]);\n\t}\n\treturn out;\n}\n\nfunction reverseChain(chain: Point[][]): Point[][] {\n\treturn chain.map((s) => [...s].reverse()).reverse();\n}\n\nfunction addCap(out: Point[][], p: Point, t: Point, h: number, cap: StrokeCap) {\n\t// From p + h·n (left) round the end to p − h·n (right), t pointing out of the stroke.\n\tconst n = normal(t);\n\tconst from = add(p, scale(n, h));\n\tconst to = add(p, scale(n, -h));\n\tif (cap === 'round') {\n\t\tpushArc(out, p, h, Math.atan2(n[1], n[0]), -Math.PI, from, to);\n\t} else if (cap === 'square') {\n\t\tconst a = add(from, scale(t, h));\n\t\tconst b = add(to, scale(t, h));\n\t\tlineTo(out, from, a);\n\t\tlineTo(out, a, b);\n\t\tlineTo(out, b, to);\n\t} else {\n\t\tlineTo(out, from, to);\n\t}\n}\n\n/** Closed outline rings painting a dot (zero-length dash or subpath) with a cap. */\nfunction dotRings(p: Point, t: Point, h: number, cap: StrokeCap): Point[][][] {\n\tif (cap === 'butt') return [];\n\tconst ring: Point[][] = [];\n\taddCap(ring, p, t, h, cap);\n\taddCap(ring, p, [-t[0], -t[1]], h, cap);\n\treturn [ring];\n}\n\ninterface StrokeStyle extends JoinStyle {\n\tcap: StrokeCap;\n}\n\n/** Raw outline rings (overlapping, to be filled with `nonzero`) of one subpath. */\nfunction strokeContour(contour: BezierContour, h: number, style: StrokeStyle, tol: number): Point[][][] {\n\tconst tiny = Math.max(1e-12, tol * 1e-6);\n\tconst all = contour.closed ? closeContour(contour) : contour.segments;\n\tconst segs = all.filter((s) => !isDegenerate(s, tiny));\n\tif (segs.length === 0) {\n\t\tconst p = contour.segments[0]?.[0];\n\t\treturn p ? dotRings(p, [1, 0], h, style.cap) : [];\n\t}\n\tif (contour.closed) {\n\t\tconst left = offsetChain(segs, h, true, style, tol);\n\t\tconst right = offsetChain(segs, -h, true, style, tol);\n\t\treturn [left, reverseChain(right)];\n\t}\n\tconst left = offsetChain(segs, h, false, style, tol);\n\tconst right = reverseChain(offsetChain(segs, -h, false, style, tol));\n\tconst ring: Point[][] = [...left];\n\tconst last = segs[segs.length - 1];\n\taddCap(ring, last[last.length - 1], segmentTangent(last, 1), h, style.cap);\n\tconst joinTo = (p: Point) => {\n\t\tconst at = ring[ring.length - 1][ring[ring.length - 1].length - 1];\n\t\tlineTo(ring, at, p);\n\t};\n\tif (right.length > 0) joinTo(right[0][0]);\n\tring.push(...right);\n\tconst first = segs[0];\n\tconst t0 = segmentTangent(first, 0);\n\taddCap(ring, first[0], [-t0[0], -t0[1]], h, style.cap);\n\tif (ring.length > 0) joinTo(ring[0][0]);\n\treturn [ring];\n}\n\nfunction ringsToPath(rings: Point[][][]): BezierPath {\n\treturn contoursToPath(rings.filter((r) => r.length > 0).map((segments) => ({ segments, closed: true })));\n}\n\n// ---------------------------------------------------------------------------\n// Dashes\n// ---------------------------------------------------------------------------\n\n/** More dashes than this draws the line solid (a tiny pattern on a long path). */\nconst MAX_DASHES = 200_000;\nconst MIN_ENTRY = 1e-6;\n\nexport interface DashedPath {\n\t/** The dashes, as open subpaths. */\n\tcontours: BezierContour[];\n\t/** Zero-length dashes: where they sit and the path direction there. */\n\tdots: { point: Point; tangent: Point }[];\n}\n\n/**\n * Applies a dash pattern (SVG `stroke-dasharray` / `stroke-dashoffset` semantics) to the\n * subpaths. Returns `null` when the pattern is invalid (negative or non-finite entries),\n * has no positive entry, or would make more than 200,000 dashes: the line is solid.\n */\nexport function dashContours(contours: readonly BezierContour[], pattern: readonly number[], offset = 0): DashedPath | null {\n\tif (pattern.length === 0 || pattern.some((v) => !Number.isFinite(v) || v < 0) || !Number.isFinite(offset)) return null;\n\tif (!pattern.some((v) => v > MIN_ENTRY)) return null;\n\tconst pat = pattern.length % 2 === 1 ? [...pattern, ...pattern] : [...pattern];\n\tconst period = pat.reduce((s, v) => s + v, 0);\n\tconst chains = contours.map((c) => {\n\t\tconst segs = closeContour(c);\n\t\tconst lengths = segs.map((s) => segmentLength(s));\n\t\treturn { segs, lengths, total: lengths.reduce((s, v) => s + v, 0), closed: c.closed };\n\t});\n\tconst totalLength = chains.reduce((s, c) => s + c.total, 0);\n\tif ((totalLength / period) * pat.length > MAX_DASHES) return null;\n\tlet off = ((offset % period) + period) % period;\n\tlet ix0 = 0;\n\twhile (off > pat[ix0]) {\n\t\toff -= pat[ix0];\n\t\tix0 = (ix0 + 1) % pat.length;\n\t}\n\tconst out: DashedPath = { contours: [], dots: [] };\n\tfor (const chain of chains) {\n\t\tif (chain.segs.length === 0 || !(chain.total > 0)) continue;\n\t\tlet ix = ix0;\n\t\tlet rem = pat[ix0] - off;\n\t\tlet pos = 0;\n\t\tconst dashes: { from: number; to: number }[] = [];\n\t\tlet guard = 0;\n\t\twhile (pos < chain.total && guard++ < MAX_DASHES * 2) {\n\t\t\tconst step = Math.min(rem, chain.total - pos);\n\t\t\tconst on = ix % 2 === 0;\n\t\t\tif (on) dashes.push({ from: pos, to: pos + step });\n\t\t\tpos += step;\n\t\t\trem -= step;\n\t\t\tif (rem <= 1e-12) {\n\t\t\t\tix = (ix + 1) % pat.length;\n\t\t\t\trem = pat[ix];\n\t\t\t}\n\t\t}\n\t\t// A closed subpath's last dash runs on into its first one.\n\t\tif (chain.closed && dashes.length > 1) {\n\t\t\tconst first = dashes[0];\n\t\t\tconst last = dashes[dashes.length - 1];\n\t\t\tif (first.from <= 1e-9 && last.to >= chain.total - 1e-9 && first.to > first.from && last.to > last.from) {\n\t\t\t\tdashes.pop();\n\t\t\t\tconst tail = trimSegments(chain.segs, last.from, chain.total, chain.lengths);\n\t\t\t\tconst head = trimSegments(chain.segs, 0, first.to, chain.lengths);\n\t\t\t\tdashes.shift();\n\t\t\t\tout.contours.push({ segments: [...tail, ...head], closed: false });\n\t\t\t}\n\t\t}\n\t\tfor (const dash of dashes) {\n\t\t\tif (dash.to - dash.from <= 1e-12) {\n\t\t\t\tconst at = locate(chain.segs, chain.lengths, dash.from);\n\t\t\t\tout.dots.push(at);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst segments = trimSegments(chain.segs, dash.from, dash.to, chain.lengths);\n\t\t\tif (segments.length > 0) out.contours.push({ segments, closed: false });\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction locate(segs: BezierSegment[], lengths: number[], at: number): { point: Point; tangent: Point } {\n\tlet acc = 0;\n\tfor (let i = 0; i < segs.length; i++) {\n\t\tif (at <= acc + lengths[i] || i === segs.length - 1) {\n\t\t\tconst piece = trimSegments([segs[i]], 0, Math.max(0, at - acc), [lengths[i]]);\n\t\t\tconst t = piece.length > 0 ? 1 : 0;\n\t\t\tconst point = piece.length > 0 ? piece[piece.length - 1][piece[piece.length - 1].length - 1] : segs[i][0];\n\t\t\tconst tangent = segmentTangent(piece.length > 0 ? piece[piece.length - 1] : segs[i], t);\n\t\t\treturn { point, tangent };\n\t\t}\n\t\tacc += lengths[i];\n\t}\n\treturn { point: segs[0][0], tangent: segmentTangent(segs[0], 0) };\n}\n\n// ---------------------------------------------------------------------------\n// Result cleanup\n// ---------------------------------------------------------------------------\n\n/** A point deep inside a face (ring plus its direct child rings): the middle of its widest span. */\nfunction deepPoint(rings: BezierSegment[][]): Point | null {\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const p of rings[0].flat()) {\n\t\tminX = Math.min(minX, p[0]);\n\t\tmaxX = Math.max(maxX, p[0]);\n\t\tminY = Math.min(minY, p[1]);\n\t\tmaxY = Math.max(maxY, p[1]);\n\t}\n\tconst w = maxX - minX;\n\tconst h = maxY - minY;\n\tif (!(w > 0 && h > 0)) return null;\n\tconst tolerance = Math.max(1e-6, 0.002 * Math.min(w, h));\n\tconst polys = rings.map((r) => flattenChain(r, tolerance));\n\tlet best: { span: number; p: Point } | null = null;\n\tfor (const f of [0.5, 0.3, 0.7, 0.15, 0.85]) {\n\t\tconst y = minY + h * f;\n\t\tconst xs: number[] = [];\n\t\tfor (const poly of polys) {\n\t\t\tfor (let i = 0; i < poly.length; i++) {\n\t\t\t\tconst a = poly[i];\n\t\t\t\tconst b = poly[(i + 1) % poly.length];\n\t\t\t\tif (a[1] <= y !== b[1] <= y) xs.push(a[0] + ((y - a[1]) / (b[1] - a[1])) * (b[0] - a[0]));\n\t\t\t}\n\t\t}\n\t\txs.sort((p, q) => p - q);\n\t\tfor (let i = 0; i + 1 < xs.length; i += 2) {\n\t\t\tconst span = xs[i + 1] - xs[i];\n\t\t\tif (!best || span > best.span) best = { span, p: [(xs[i] + xs[i + 1]) / 2, y] };\n\t\t}\n\t}\n\treturn best?.p ?? null;\n}\n\n/** Keeps the faces that `spurious` does not flag (a flagged face drops its descendants too). */\nfunction dropSpurious(rings: NestedRing[], spurious: (face: BezierSegment[][], ring: NestedRing) => boolean): BezierPath {\n\tconst children = rings.map(() => [] as number[]);\n\trings.forEach((r, i) => {\n\t\tif (r.parent >= 0) children[r.parent].push(i);\n\t});\n\tconst marked = rings.map((r, i) => spurious([r.segments, ...children[i].map((c) => rings[c].segments)], r));\n\tconst hasMarkedAncestor = (i: number): boolean => {\n\t\tfor (let k = i; k >= 0; k = rings[k].parent) if (marked[k]) return true;\n\t\treturn false;\n\t};\n\treturn nestedRingsToPath(rings, (i) => !hasMarkedAncestor(i));\n}\n\n/**\n * Merges runs of tangent-continuous curve segments that one cubic fits within `tolerance`\n * (the stroker's many small offset pieces), keeping corners.\n */\nfunction mergeSmoothRuns(path: BezierPath, tolerance: number): BezierPath {\n\tconst contours = pathToContours(path).map((contour) => {\n\t\tconst segs = closeContour(contour, true).map((s) => (s.length === 3 ? raise(s) : [...s]));\n\t\tconst n = segs.length;\n\t\tif (n < 2) return { segments: segs, closed: true };\n\t\tconst smooth = (a: BezierSegment, b: BezierSegment) => {\n\t\t\tif (a.length !== 4 || b.length !== 4) return false;\n\t\t\tconst ta = segmentTangent(a, 1);\n\t\t\tconst tb = segmentTangent(b, 0);\n\t\t\treturn dot(ta, tb) > 0.9998 && Math.abs(cross(ta, tb)) < 0.02;\n\t\t};\n\t\tlet start = 0;\n\t\tfor (let k = 0; k < n; k++) {\n\t\t\tif (!smooth(segs[(k + n - 1) % n], segs[k])) {\n\t\t\t\tstart = k;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst ordered = [...segs.slice(start), ...segs.slice(0, start)];\n\t\tconst out: BezierSegment[] = [];\n\t\tlet i = 0;\n\t\twhile (i < ordered.length) {\n\t\t\tlet current = ordered[i];\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < ordered.length && smooth(ordered[j - 1], ordered[j])) {\n\t\t\t\tconst run = ordered.slice(i, j + 1);\n\t\t\t\tconst pts: Point[] = [];\n\t\t\t\trun.forEach((s, k) => {\n\t\t\t\t\tfor (let q = k === 0 ? 0 : 1; q <= 12; q++) pts.push(segmentPoint(s, q / 12));\n\t\t\t\t});\n\t\t\t\tconst fit = fitCubicRun(pts, tolerance, segmentTangent(run[0], 0), segmentTangent(run[run.length - 1], 1));\n\t\t\t\tif (fit.length !== 1) break;\n\t\t\t\tconst c = fit[0];\n\t\t\t\tcurrent = [run[0][0], c.p1, c.p2, run[run.length - 1][3]];\n\t\t\t\tj++;\n\t\t\t}\n\t\t\tout.push(current);\n\t\t\ti = j;\n\t\t}\n\t\treturn { segments: out, closed: true };\n\t});\n\treturn contoursToPath(contours);\n}\n\n// ---------------------------------------------------------------------------\n// Public operations\n// ---------------------------------------------------------------------------\n\nfunction joinStyle(join: StrokeJoin | undefined, miterLimit: number | undefined): JoinStyle {\n\treturn {\n\t\tjoin: join ?? 'miter',\n\t\tmiterLimit: miterLimit !== undefined && Number.isFinite(miterLimit) ? Math.max(1, miterLimit) : 4\n\t};\n}\n\n/**\n * The raw (overlapping) outline of a stroke as closed subpaths to fill with `nonzero`.\n * `outlineStroke` normalises it; this is useful for previews.\n */\nexport function strokeOutlineRings(input: BezierPathInput, options: OutlineStrokeOptions): BezierPath {\n\tconst h = options.width / 2;\n\tif (!(h > 0) || !Number.isFinite(h)) return [];\n\tconst tol = options.tolerance && options.tolerance > 0 ? options.tolerance : DEFAULT_TOLERANCE;\n\tconst style: StrokeStyle = { ...joinStyle(options.join, options.miterLimit), cap: options.cap ?? 'butt' };\n\tlet contours = pathToContours(input);\n\tconst rings: Point[][][] = [];\n\tif (options.dash && options.dash.length > 0) {\n\t\tconst dashed = dashContours(contours, options.dash, options.dashOffset ?? 0);\n\t\tif (dashed) {\n\t\t\tcontours = dashed.contours;\n\t\t\tfor (const dot of dashed.dots) rings.push(...dotRings(dot.point, dot.tangent, h, style.cap));\n\t\t}\n\t}\n\tfor (const contour of contours) rings.push(...strokeContour(contour, h, style, tol));\n\treturn ringsToPath(rings);\n}\n\n/**\n * Outline Stroke: the filled area painted by stroking `path`, as one clean path (overlaps\n * removed, consistently oriented). Joins, caps, miter limit and dashes follow SVG.\n */\nexport function outlineStroke(input: BezierPathInput, options: OutlineStrokeOptions): BezierPath {\n\tconst h = options.width / 2;\n\tif (!(h > 0) || !Number.isFinite(h)) return [];\n\tlet source: BezierPath;\n\ttry {\n\t\tsource = contoursToPath(pathToContours(input));\n\t} catch {\n\t\treturn [];\n\t}\n\tif (source.some((c) => c.type !== 'Z' && Object.values(c).some((v) => typeof v === 'number' && !Number.isFinite(v)))) return [];\n\tconst raw = strokeOutlineRings(source, options);\n\tif (raw.length === 0) return [];\n\tconst tol = options.tolerance && options.tolerance > 0 ? options.tolerance : DEFAULT_TOLERANCE;\n\tconst sourceContours = pathToContours(source);\n\tconst operand: PathOperandInput = { path: raw, fillRule: 'nonzero' };\n\ttry {\n\t\tconst rings = pathBooleanRings('union', [operand], options);\n\t\tconst cleaned = dropSpurious(rings, (face, ring) => {\n\t\t\tif (ring.area >= 0) return false;\n\t\t\tconst p = deepPoint(face);\n\t\t\treturn !!p && distanceToBezierPath(sourceContours, p) < h * (1 - 1e-4) - 1e-6;\n\t\t});\n\t\treturn mergeSmoothRuns(cleaned, tol);\n\t} catch {\n\t\treturn pathBoolean('union', [operand], options).path;\n\t}\n}\n\n/**\n * Offset Path: grows (`distance > 0`) or shrinks (`distance < 0`) the filled area by\n * `distance`. Closed subpaths are a filled region (read with `fillRule`); open subpaths are\n * outlined with a stroke of width `2·|distance|` and butt caps (when growing). Loops of the\n * offset curves are removed.\n */\nexport function offsetPath(input: BezierPathInput, distance: number, options: OffsetPathOptions = {}): BezierPath {\n\tif (!Number.isFinite(distance)) return [];\n\tconst contours = pathToContours(input);\n\tif (contours.some((c) => c.segments.some((s) => s.some((p) => !Number.isFinite(p[0]) || !Number.isFinite(p[1]))))) return [];\n\tconst closed = contours.filter((c) => c.closed && c.segments.length > 0);\n\tconst open = contours.filter((c) => !c.closed && c.segments.length > 0);\n\tconst fillRule = options.fillRule ?? 'nonzero';\n\tconst fill: PathOperandInput = { path: contoursToPath(closed), fillRule };\n\tconst d = Math.abs(distance);\n\tconst tol = options.tolerance && options.tolerance > 0 ? options.tolerance : DEFAULT_TOLERANCE;\n\tif (d === 0) return pathBoolean('union', [fill], options).path;\n\tconst strokeOptions: OutlineStrokeOptions = { width: 2 * d, join: options.join ?? 'miter', miterLimit: options.miterLimit, cap: 'butt', tolerance: tol };\n\tconst ring: PathOperandInput = { path: strokeOutlineRings(contoursToPath(closed), strokeOptions), fillRule: 'nonzero' };\n\tconst openRing: PathOperandInput = {\n\t\tpath: distance > 0 ? strokeOutlineRings(contoursToPath(open), strokeOptions) : [],\n\t\tfillRule: 'nonzero'\n\t};\n\tconst grow = distance > 0;\n\tconst operands = [fill, ring, openRing];\n\ttry {\n\t\tconst rings = pathBooleanRings(grow ? (m) => m[0] || m[1] || m[2] : (m) => m[0] && !m[1], operands, options);\n\t\tconst sourceContours = contours;\n\t\tconst cleaned = dropSpurious(rings, (face, r) => {\n\t\t\tif (grow ? r.area >= 0 : r.area <= 0) return false;\n\t\t\tconst p = deepPoint(face);\n\t\t\treturn !!p && distanceToBezierPath(sourceContours, p) < d * (1 - 1e-4) - 1e-6;\n\t\t});\n\t\treturn mergeSmoothRuns(cleaned, tol);\n\t} catch {\n\t\treturn grow\n\t\t\t? pathBoolean('union', operands, options).path\n\t\t\t: pathBoolean('difference', [fill, ring], options).path;\n\t}\n}\n","/**\n * Planar maps: the faces and edges a set of paths divides the plane into (Shape Builder, fill\n * regions, Pathfinder Outline).\n *\n * Unlike `dividePaths`, which splits the *filled areas* of closed shapes, every path here is an\n * edge, open ones included: three crossing lines enclose a triangle, a line across a rectangle\n * splits it in two, and an area enclosed by paths is a face whether or not anything fills it.\n *\n * 1. `planarSplit` (the boolean engine's intersection search) cuts every input segment wherever\n * it meets another one; coincident pieces become one edge.\n * 2. Each edge gives two half-edges. Around every vertex the half-edges leaving it are sorted by\n * the direction in which they cross a small circle around it (half the shortest edge there),\n * which orders tangent curves correctly.\n * 3. A face walk takes, at each vertex, the half-edge just clockwise of the way back — the\n * sharpest left turn. Bounded faces come out counter-clockwise (positive area in a y-up\n * frame), the outer boundary of every connected piece clockwise; spurs (loose line ends) are\n * dropped. Each outer boundary becomes a hole of the smallest face of another piece around\n * it, so nesting is decided face by face, never by an even-odd rule over the whole network.\n * 4. Faces learn which inputs fill them from a point well inside each face.\n *\n * Adjacency is kept in per-vertex lists and half-edge slots (no scans over all edges per step).\n *\n * Ported from vectorcraft-main/crates/pathops/src/planar.rs (`map`, `Map::merge`, `build`,\n * `interior_point`, `without_spurs`, `leaving_angle`, `cut_out`), figcraft-main/crates/doc/src/vector_faces.rs\n * (`faces`: half-edge walk with an angle-sorted next edge, the winding deciding inside or\n * outside) and cadcraft-main/crates/geom/src/region.rs (`enclosing_loop`, `boundary_at_gap`:\n * a ray from the point, the sharpest left turn, islands as holes, gaps between open ends\n * closed). MIT OR Apache-2.0, © ArtCraft Team and contributors. See THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from '../types.js';\nimport type { PolygonFillRule } from '../polygonBoolean.js';\nimport {\n\tcloseContour,\n\tcontourSignedArea,\n\tcontoursToPath,\n\tdist,\n\tflattenChain,\n\tpathToContours,\n\tsegmentLength,\n\tsegmentNearest,\n\tsegmentPoint,\n\tsegmentTangent,\n\tsolveMonotone,\n\tsubSegment,\n\twindingNumber,\n\ttype BezierContour,\n\ttype BezierPath,\n\ttype BezierPathInput,\n\ttype BezierSegment\n} from './bezier.js';\nimport { planarSplit, type PathOpsOptions, type PlanarSplitEdge } from './boolean.js';\n\n/** One input of a planar map. */\nexport interface PlanarInput {\n\t/** Its subpaths: closed ones bound faces and the area the input fills, open ones are edges only. */\n\tpath: BezierPathInput;\n\t/** How its closed subpaths fill (default `nonzero`). */\n\tfillRule?: PolygonFillRule;\n\t/** Whether its closed subpaths count as filling the faces inside them (default true). */\n\tfills?: boolean;\n}\n\nexport interface PlanarMapOptions extends PathOpsOptions {\n\t/**\n\t * Close gaps up to this distance between loose ends (vertices with one edge): each one joins\n\t * the nearest other loose end within reach by a straight edge (default 0, off).\n\t */\n\tgap?: number;\n\t/** Most input segments mapped (default 4096); a bigger input gives no map. */\n\tmaxSegments?: number;\n}\n\n/** A bounded face: an outer outline with the holes other pieces of the map make in it. */\nexport interface PlanarFace {\n\t/** Outer subpath (counter-clockwise in a y-up frame) followed by its holes (clockwise). */\n\tpath: BezierPath;\n\t/** Area (holes taken out). */\n\tarea: number;\n\tbounds: { minX: number; minY: number; maxX: number; maxY: number };\n}\n\n/** A run of edges between junctions (vertices where paths meet or a path ends). */\nexport interface PlanarChain {\n\tsegments: BezierSegment[];\n\t/** A loop with no junction on it. */\n\tclosed: boolean;\n\t/** The input it lies on: the front-most (highest index) where several coincide. */\n\tsource: number;\n}\n\ninterface Edge extends PlanarSplitEdge {\n\t/** Front-most input along it (-1: an edge closing a gap). */\n\tsource: number;\n}\n\ninterface Cycle {\n\thalves: number[];\n\tsegments: BezierSegment[];\n\tarea: number;\n\tbounds: PlanarFace['bounds'];\n}\n\nconst twin = (h: number) => h ^ 1;\n\nfunction boundsOf(segs: readonly BezierSegment[]): PlanarFace['bounds'] {\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const s of segs) {\n\t\tfor (const [x, y] of s) {\n\t\t\tif (x < minX) minX = x;\n\t\t\tif (x > maxX) maxX = x;\n\t\t\tif (y < minY) minY = y;\n\t\t\tif (y > maxY) maxY = y;\n\t\t}\n\t}\n\treturn { minX, minY, maxX, maxY };\n}\n\nconst inBounds = (b: PlanarFace['bounds'], p: Point) => p[0] >= b.minX && p[0] <= b.maxX && p[1] >= b.minY && p[1] <= b.maxY;\n\n/** `cycle` without the spurs it runs out and back along (a line's loose end). */\nfunction withoutSpurs(cycle: readonly number[]): number[] {\n\tconst out: number[] = [];\n\tfor (const h of cycle) {\n\t\tif (out.length && out[out.length - 1] === twin(h)) out.pop();\n\t\telse out.push(h);\n\t}\n\twhile (out.length >= 2 && twin(out[0]) === out[out.length - 1]) {\n\t\tout.pop();\n\t\tout.shift();\n\t}\n\treturn out;\n}\n\n/** Moves the end points of a segment onto `a` and `b`, the handles next to them moving along. */\nfunction pinEnds(pts: readonly Point[], a: Point, b: Point): Point[] {\n\tconst n = pts.length;\n\tif (n === 2) return [a, b];\n\tconst out = pts.map((p) => [p[0], p[1]] as Point);\n\tconst d0: Point = [a[0] - pts[0][0], a[1] - pts[0][1]];\n\tconst d1: Point = [b[0] - pts[n - 1][0], b[1] - pts[n - 1][1]];\n\tout[0] = a;\n\tout[n - 1] = b;\n\tif (n === 3) {\n\t\tout[1] = [pts[1][0] + (d0[0] + d1[0]) / 2, pts[1][1] + (d0[1] + d1[1]) / 2];\n\t} else {\n\t\tout[1] = [pts[1][0] + d0[0], pts[1][1] + d0[1]];\n\t\tout[2] = [pts[2][0] + d1[0], pts[2][1] + d1[1]];\n\t}\n\treturn out;\n}\n\n/**\n * A point strictly inside a filled path (nonzero), well away from its edges where it can be:\n * the middle of the widest span of a few horizontal lines through it. Null for an empty path.\n * Ported from vectorcraft's `interior_point` (cadcraft's `interior_point` for polygons).\n */\nexport function interiorPoint(path: BezierPathInput | readonly (readonly BezierSegment[])[]): Point | null {\n\tconst chains: BezierSegment[][] =\n\t\tArray.isArray(path) && path.length > 0 && Array.isArray(path[0]) && Array.isArray((path[0] as BezierSegment[])[0]?.[0])\n\t\t\t? (path as BezierSegment[][])\n\t\t\t: pathToContours(path as BezierPathInput).map((c) => closeContour(c, true));\n\tconst b = boundsOf(chains.flat());\n\tif (!(b.maxX > b.minX) || !(b.maxY > b.minY)) return null;\n\tconst tol = Math.max(b.maxX - b.minX, b.maxY - b.minY) * 1e-3;\n\tconst rings = chains.filter((c) => c.length).map((c) => flattenChain(c, tol));\n\tlet best: { width: number; p: Point } | null = null;\n\tfor (const f of [0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, 0.0625, 0.9375]) {\n\t\t// A little off round fractions, which on art drawn to a grid fall on the height of a corner.\n\t\tconst y = b.minY + (b.maxY - b.minY) * (f + 0.0061803);\n\t\tconst xs: number[] = [];\n\t\tfor (const ring of rings) {\n\t\t\tfor (let i = 0; i < ring.length; i++) {\n\t\t\t\tconst a = ring[i];\n\t\t\t\tconst c = ring[(i + 1) % ring.length];\n\t\t\t\tif (a[1] <= y !== c[1] <= y) xs.push(a[0] + ((y - a[1]) / (c[1] - a[1])) * (c[0] - a[0]));\n\t\t\t}\n\t\t}\n\t\txs.sort((p, q) => p - q);\n\t\tfor (let i = 0; i + 1 < xs.length; i++) {\n\t\t\tconst width = xs[i + 1] - xs[i];\n\t\t\tif (!(width > (best?.width ?? 0))) continue;\n\t\t\tconst m: Point = [(xs[i] + xs[i + 1]) / 2, y];\n\t\t\tif (windingNumber(chains, m) !== 0) best = { width, p: m };\n\t\t}\n\t}\n\treturn best?.p ?? null;\n}\n\n/**\n * The planar map of a set of paths. Build it with `PlanarMap.build`; faces, chains and fills\n * are computed on first use.\n */\nexport class PlanarMap {\n\tprivate readonly around: number[][];\n\tprivate readonly slot: number[];\n\tprivate readonly component: number[];\n\tprivate cyclesCache: { faces: Cycle[]; holes: Cycle[]; faceHoles: number[][]; faceOf: Int32Array } | null = null;\n\tprivate sourcesCache: (number[] | null)[] = [];\n\tprivate readonly fills: { chains: BezierSegment[][]; fillRule: PolygonFillRule; bounds: PlanarFace['bounds'] }[];\n\n\tprivate constructor(\n\t\treadonly vertices: Point[],\n\t\tprivate readonly edges: Edge[],\n\t\tprivate readonly sources: BezierSegment[],\n\t\treadonly eps: number,\n\t\treadonly inputCount: number,\n\t\tinputs: readonly PlanarInput[]\n\t) {\n\t\tthis.fills = inputs.map((input) => {\n\t\t\tconst chains = input.fills === false ? [] : pathToContours(input.path).filter((c) => c.closed).map((c) => closeContour(c));\n\t\t\treturn { chains, fillRule: input.fillRule ?? 'nonzero', bounds: boundsOf(chains.flat()) };\n\t\t});\n\t\tconst nv = vertices.length;\n\t\tthis.around = Array.from({ length: nv }, () => []);\n\t\tfor (let h = 0; h < edges.length * 2; h++) this.around[this.origin(h)].push(h);\n\t\tthis.slot = new Array(edges.length * 2).fill(0);\n\t\tthis.around.forEach((list, v) => {\n\t\t\tif (list.length === 0) return;\n\t\t\tconst at = vertices[v];\n\t\t\t// Half the shortest chord here: every edge gets that far from the vertex.\n\t\t\tlet r = Infinity;\n\t\t\tfor (const h of list) r = Math.min(r, dist(at, vertices[this.origin(twin(h))]));\n\t\t\tr *= 0.5;\n\t\t\tconst keyed = list.map((h) => ({ h, angle: this.leavingAngle(h, at, r) }));\n\t\t\tkeyed.sort((a, b) => a.angle - b.angle);\n\t\t\tlist.length = 0;\n\t\t\tkeyed.forEach(({ h }, k) => {\n\t\t\t\tlist.push(h);\n\t\t\t\tthis.slot[h] = k;\n\t\t\t});\n\t\t});\n\t\t// Connected pieces (union-find over the edges).\n\t\tconst parent = Array.from({ length: nv }, (_, i) => i);\n\t\tconst find = (v: number): number => {\n\t\t\twhile (parent[v] !== v) {\n\t\t\t\tparent[v] = parent[parent[v]];\n\t\t\t\tv = parent[v];\n\t\t\t}\n\t\t\treturn v;\n\t\t};\n\t\tfor (const e of edges) parent[find(e.a)] = find(e.b);\n\t\tthis.component = Array.from({ length: nv }, (_, v) => find(v));\n\t}\n\n\t/**\n\t * Maps `inputs` (their subpaths as given: open ones stay open). Null when they cannot be mapped\n\t * (non-finite points, more than `maxSegments` segments or an intersection search that does\n\t * not settle) or have no edges.\n\t */\n\tstatic build(inputs: readonly PlanarInput[], options: PlanarMapOptions = {}): PlanarMap | null {\n\t\tconst chains = inputs.map((input) => pathToContours(input.path).map((c) => closeContour(c)).filter((c) => c.length));\n\t\tconst count = chains.reduce((n, cs) => n + cs.reduce((m, c) => m + c.length, 0), 0);\n\t\tif (count === 0 || count > (options.maxSegments ?? 4096)) return null;\n\t\tlet split;\n\t\ttry {\n\t\t\tsplit = planarSplit(chains, options);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t\tconst edges: Edge[] = split.edges.filter((e) => e.a !== e.b).map((e) => ({ ...e, source: e.operands[e.operands.length - 1] ?? -1 }));\n\t\tif (!edges.length) return null;\n\t\tconst gap = options.gap ?? 0;\n\t\tif (gap > split.eps) closeGaps(split.vertices, edges, gap);\n\t\treturn new PlanarMap(split.vertices, edges, split.sources, split.eps, inputs.length, inputs);\n\t}\n\n\t// -- half-edges ------------------------------------------------------------\n\n\tprivate origin(h: number): number {\n\t\tconst e = this.edges[h >> 1];\n\t\treturn h & 1 ? e.b : e.a;\n\t}\n\n\tprivate halfPts(h: number): Point[] {\n\t\tconst pts = this.edges[h >> 1].pts;\n\t\treturn h & 1 ? [...pts].reverse() : pts;\n\t}\n\n\t/** The half-edge after `h` on the face to its left: the sharpest left turn at its end. */\n\tprivate next(h: number): number {\n\t\tconst back = twin(h);\n\t\tconst list = this.around[this.origin(back)];\n\t\treturn list[(this.slot[back] + list.length - 1) % list.length];\n\t}\n\n\t/** Direction (angle) in which half-edge `h`, leaving `at`, first gets `r` away from it. */\n\tprivate leavingAngle(h: number, at: Point, r: number): number {\n\t\tconst pts = this.halfPts(h);\n\t\tconst d = (t: number) => dist(segmentPoint(pts, t), at);\n\t\tlet lo = 0;\n\t\tlet hi = 1;\n\t\tconst steps = pts.length === 2 ? 1 : 16;\n\t\tfor (let k = 1; k <= steps; k++) {\n\t\t\tif (d(k / steps) >= r) {\n\t\t\t\thi = k / steps;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlo = k / steps;\n\t\t}\n\t\tfor (let i = 0; i < 40 && hi - lo > 1e-12; i++) {\n\t\t\tconst m = (lo + hi) / 2;\n\t\t\tif (d(m) >= r) hi = m;\n\t\t\telse lo = m;\n\t\t}\n\t\tconst p = segmentPoint(pts, hi);\n\t\tif (dist(p, at) > 0) return Math.atan2(p[1] - at[1], p[0] - at[0]);\n\t\tconst t = segmentTangent(pts, 0);\n\t\treturn Math.atan2(t[1], t[0]);\n\t}\n\n\t/** Segments of a run of half-edges (a loop unless `closed` is false), pieces of one input segment joined again. */\n\tprivate cycleSegments(halves: readonly number[], closed = true): BezierSegment[] {\n\t\tinterface Run {\n\t\t\th: number;\n\t\t\tsrc: number;\n\t\t\tfrom: number;\n\t\t\tto: number;\n\t\t}\n\t\tconst runs: Run[] = halves.map((h) => {\n\t\t\tconst e = this.edges[h >> 1];\n\t\t\treturn { h, src: e.source < 0 ? -1 : e.src, from: h & 1 ? e.sb : e.sa, to: h & 1 ? e.sa : e.sb };\n\t\t});\n\t\tconst joins = (a: Run, b: Run) => 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;\n\t\tconst n = runs.length;\n\t\tlet start = 0;\n\t\tfor (let k = 0; closed && k < n; k++) {\n\t\t\tif (!joins(runs[(k + n - 1) % n], runs[k])) {\n\t\t\t\tstart = k;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst ordered = [...runs.slice(start), ...runs.slice(0, start)];\n\t\tconst out: BezierSegment[] = [];\n\t\tlet i = 0;\n\t\twhile (i < ordered.length) {\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < ordered.length && joins(ordered[j - 1], ordered[j])) j++;\n\t\t\tconst first = ordered[i];\n\t\t\tconst last = ordered[j - 1];\n\t\t\tif (j - i === 1) out.push(this.halfPts(first.h));\n\t\t\telse {\n\t\t\t\tconst a = this.vertices[this.origin(first.h)];\n\t\t\t\tconst b = this.vertices[this.origin(twin(last.h))];\n\t\t\t\tconst seg = this.sources[first.src];\n\t\t\t\tout.push(seg.length === 2 ? [a, b] : pinEnds(subSegment(seg, first.from, last.to), a, b));\n\t\t\t}\n\t\t\ti = j;\n\t\t}\n\t\treturn out;\n\t}\n\n\tprivate cycles() {\n\t\tif (this.cyclesCache) return this.cyclesCache;\n\t\tconst halves = this.edges.length * 2;\n\t\tconst seen = new Uint8Array(halves);\n\t\tconst faces: Cycle[] = [];\n\t\tconst holes: Cycle[] = [];\n\t\tconst sliver = this.eps * 10;\n\t\tfor (let start = 0; start < halves; start++) {\n\t\t\tif (seen[start]) continue;\n\t\t\tconst walk: number[] = [];\n\t\t\tlet h = start;\n\t\t\twhile (!seen[h]) {\n\t\t\t\tseen[h] = 1;\n\t\t\t\twalk.push(h);\n\t\t\t\th = this.next(h);\n\t\t\t}\n\t\t\tconst cycle = withoutSpurs(walk);\n\t\t\tif (!cycle.length) continue;\n\t\t\tconst segments = this.cycleSegments(cycle);\n\t\t\tconst area = contourSignedArea(segments);\n\t\t\tlet perimeter = 0;\n\t\t\tfor (const s of segments) perimeter += s.length === 2 ? dist(s[0], s[1]) : segmentLength(s, 0, 1, 1e-6);\n\t\t\tif (!(perimeter > 0)) continue;\n\t\t\tconst entry: Cycle = { halves: cycle, segments, area, bounds: boundsOf(segments) };\n\t\t\tif (area > 0 && (2 * area) / perimeter >= sliver) faces.push(entry);\n\t\t\telse if (area < 0 && (-2 * area) / perimeter >= sliver) holes.push(entry);\n\t\t}\n\t\t// Each outer boundary is a hole in the smallest face of another piece around it.\n\t\tconst faceHoles: number[][] = faces.map(() => []);\n\t\tholes.forEach((hole, k) => {\n\t\t\tconst v = this.origin(hole.halves[0]);\n\t\t\tconst p = this.vertices[v];\n\t\t\tconst own = this.component[v];\n\t\t\tlet best = -1;\n\t\t\tfaces.forEach((face, i) => {\n\t\t\t\tif (this.component[this.origin(face.halves[0])] === own || !inBounds(face.bounds, p)) return;\n\t\t\t\tif (best >= 0 && faces[best].area <= face.area) return;\n\t\t\t\tif (windingNumber([face.segments], p) !== 0) best = i;\n\t\t\t});\n\t\t\tif (best >= 0) faceHoles[best].push(k);\n\t\t});\n\t\tconst faceOf = new Int32Array(halves).fill(-1);\n\t\tfaces.forEach((face, i) => {\n\t\t\tfor (const h of face.halves) faceOf[h] = i;\n\t\t\tfor (const k of faceHoles[i]) for (const h of holes[k].halves) faceOf[h] = i;\n\t\t});\n\t\tthis.cyclesCache = { faces, holes, faceHoles, faceOf };\n\t\tthis.sourcesCache = faces.map(() => null);\n\t\treturn this.cyclesCache;\n\t}\n\n\tprivate faceChains(i: number): BezierSegment[][] {\n\t\tconst { faces, holes, faceHoles } = this.cycles();\n\t\treturn [faces[i].segments, ...faceHoles[i].map((k) => holes[k].segments)];\n\t}\n\n\t/** The bounded faces of the map. */\n\tget faces(): PlanarFace[] {\n\t\tconst { faces, holes, faceHoles } = this.cycles();\n\t\treturn faces.map((f, i) => {\n\t\t\tconst chains = this.faceChains(i);\n\t\t\tconst area = f.area + faceHoles[i].reduce((sum, k) => sum + holes[k].area, 0);\n\t\t\treturn { path: contoursToPath(chains.map((segments) => ({ segments, closed: true }))), area, bounds: f.bounds };\n\t\t});\n\t}\n\n\t/** The inputs whose fill covers face `i`, ascending (empty for an area only outlines enclose). */\n\tfaceSources(i: number): number[] {\n\t\tthis.cycles();\n\t\tconst cached = this.sourcesCache[i];\n\t\tif (cached) return cached;\n\t\tconst p = interiorPoint(this.faceChains(i));\n\t\tconst out: number[] = [];\n\t\tif (p) {\n\t\t\tthis.fills.forEach((fill, k) => {\n\t\t\t\tif (!fill.chains.length || !inBounds(fill.bounds, p)) return;\n\t\t\t\tconst w = windingNumber(fill.chains, p);\n\t\t\t\tif (fill.fillRule === 'evenodd' ? (w & 1) === 1 : w !== 0) out.push(k);\n\t\t\t});\n\t\t}\n\t\tthis.sourcesCache[i] = out;\n\t\treturn out;\n\t}\n\n\t/** A point well inside face `i`. */\n\tfaceInterior(i: number): Point | null {\n\t\treturn interiorPoint(this.faceChains(i));\n\t}\n\n\t/** The face under `p` (the smallest one around it), or -1. */\n\tfaceAt(p: Point): number {\n\t\tconst { faces } = this.cycles();\n\t\tlet best = -1;\n\t\tfaces.forEach((face, i) => {\n\t\t\tif (!inBounds(face.bounds, p) || (best >= 0 && faces[best].area <= face.area)) return;\n\t\t\tif (windingNumber(this.faceChains(i), p) !== 0) best = i;\n\t\t});\n\t\treturn best;\n\t}\n\n\t/**\n\t * The union of faces, traced along the map's own edges: those between two merged faces drop\n\t * out exactly, so no seam or sliver is left where a boolean union of their outlines could\n\t * round shared corners apart. Outer subpaths come out counter-clockwise (y-up), holes clockwise.\n\t */\n\tmerge(faceIndices: readonly number[]): BezierPath {\n\t\tconst { faceOf } = this.cycles();\n\t\tconst picked = new Set(faceIndices);\n\t\tconst merged = (h: number) => faceOf[h] >= 0 && picked.has(faceOf[h]);\n\t\tconst boundary = (h: number) => merged(h) && !merged(twin(h));\n\t\tconst used = new Uint8Array(this.edges.length * 2);\n\t\tconst contours: BezierContour[] = [];\n\t\tfor (let start = 0; start < used.length; start++) {\n\t\t\tif (used[start] || !boundary(start)) continue;\n\t\t\tconst cycle: number[] = [];\n\t\t\tlet h = start;\n\t\t\tlet ok = true;\n\t\t\twhile (!used[h]) {\n\t\t\t\tused[h] = 1;\n\t\t\t\tcycle.push(h);\n\t\t\t\t// Turn from the way back as a face walk does, past edges inside the merged faces.\n\t\t\t\tconst back = twin(h);\n\t\t\t\tconst list = this.around[this.origin(back)];\n\t\t\t\tconst k = this.slot[back];\n\t\t\t\tconst n = list.length;\n\t\t\t\tlet found = -1;\n\t\t\t\tfor (let j = 1; j <= n; j++) {\n\t\t\t\t\tconst g = list[(k - j + n * 2) % n];\n\t\t\t\t\tif (boundary(g)) {\n\t\t\t\t\t\tfound = g;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (found < 0) {\n\t\t\t\t\tok = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\th = found;\n\t\t\t}\n\t\t\tif (!ok || h !== start) continue;\n\t\t\tcontours.push({ segments: this.cycleSegments(cycle), closed: true });\n\t\t}\n\t\treturn contoursToPath(contours);\n\t}\n\n\t/**\n\t * The edges joined into runs between junctions: vertices where other than two edges meet, or\n\t * where the two edges lie on different inputs. Each run is keyed by its input.\n\t */\n\tchains(): PlanarChain[] {\n\t\tconst passes = (v: number): [number, number] | null => {\n\t\t\tconst list = this.around[v];\n\t\t\tif (list.length !== 2) return null;\n\t\t\treturn this.edges[list[0] >> 1].source === this.edges[list[1] >> 1].source ? [list[0], list[1]] : null;\n\t\t};\n\t\tconst used = new Uint8Array(this.edges.length);\n\t\tconst out: PlanarChain[] = [];\n\t\tconst walk = (h0: number) => {\n\t\t\tconst source = this.edges[h0 >> 1].source;\n\t\t\tconst halves: number[] = [];\n\t\t\tlet h = h0;\n\t\t\tlet closed = false;\n\t\t\tfor (;;) {\n\t\t\t\tif (used[h >> 1]) {\n\t\t\t\t\tclosed = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tused[h >> 1] = 1;\n\t\t\t\thalves.push(h);\n\t\t\t\tconst v = this.origin(twin(h));\n\t\t\t\tconst pass = passes(v);\n\t\t\t\tif (!pass) {\n\t\t\t\t\tclosed = v === this.origin(h0);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\th = pass[0] === twin(h) ? pass[1] : pass[0];\n\t\t\t}\n\t\t\tif (source < 0) return;\n\t\t\tconst segments = this.cycleSegments(halves, closed);\n\t\t\tout.push({ segments, closed, source });\n\t\t};\n\t\tthis.around.forEach((list, v) => {\n\t\t\tif (passes(v)) return;\n\t\t\tfor (const h of list) if (!used[h >> 1]) walk(h);\n\t\t});\n\t\tfor (let e = 0; e < this.edges.length; e++) if (!used[e]) walk(e * 2);\n\t\treturn out;\n\t}\n\n\t// -- fill regions (cadcraft `boundary_at_gap`) -------------------------------\n\n\t/**\n\t * The region enclosing `p`, traced as cadcraft does it: a ray from `p` towards +x meets the\n\t * edges in order; for each, the half-edge with `p` on its left is followed by always taking the\n\t * sharpest left turn, until a loop that runs counter-clockwise round `p` comes back. The pieces\n\t * of the map nested inside that loop (islands) become its holes. Null when nothing encloses `p`.\n\t */\n\tregionAt(p: Point): BezierPath | null {\n\t\tconst { faces, faceOf } = this.cycles();\n\t\tconst hits: { x: number; e: number }[] = [];\n\t\tthis.edges.forEach((e, i) => {\n\t\t\tconst ay = this.vertices[e.a][1];\n\t\t\tconst by = this.vertices[e.b][1];\n\t\t\tif (ay > p[1] === by > p[1]) return;\n\t\t\t// Edges are monotone pieces, so the ray crosses each at most once.\n\t\t\tconst t = solveMonotone(e.pts, 1, p[1]);\n\t\t\tconst x = segmentPoint(e.pts, t)[0];\n\t\t\tif (x > p[0]) hits.push({ x, e: i });\n\t\t});\n\t\thits.sort((a, b) => a.x - b.x);\n\t\tfor (const { e } of hits.slice(0, 64)) {\n\t\t\tconst ay = this.vertices[this.edges[e].a][1];\n\t\t\tconst by = this.vertices[this.edges[e].b][1];\n\t\t\t// `p` lies west of the edge, which is the left side (y-up frame) of the half-edge running towards +y.\n\t\t\tconst h = by > ay ? e * 2 : e * 2 + 1;\n\t\t\tconst walk: number[] = [];\n\t\t\tconst seen = new Set<number>();\n\t\t\tlet g = h;\n\t\t\twhile (!seen.has(g)) {\n\t\t\t\tseen.add(g);\n\t\t\t\twalk.push(g);\n\t\t\t\tg = this.next(g);\n\t\t\t}\n\t\t\tif (g !== h) continue;\n\t\t\tconst cycle = withoutSpurs(walk);\n\t\t\tif (!cycle.length) continue;\n\t\t\tconst segments = this.cycleSegments(cycle);\n\t\t\tif (!(contourSignedArea(segments) > 0) || windingNumber([segments], p) === 0) continue;\n\t\t\t// The loop is one of the map's faces: take its holes.\n\t\t\tconst face = faceOf[cycle[0]];\n\t\t\tif (face >= 0 && faces[face].halves.includes(cycle[0])) {\n\t\t\t\treturn contoursToPath(this.faceChains(face).map((s) => ({ segments: s, closed: true })));\n\t\t\t}\n\t\t\treturn contoursToPath([{ segments, closed: true }]);\n\t\t}\n\t\treturn null;\n\t}\n}\n\n/** Joins loose ends (degree-one vertices) to the nearest other loose end within `gap`. */\nfunction closeGaps(vertices: Point[], edges: Edge[], gap: number) {\n\tconst degree = new Array(vertices.length).fill(0);\n\tconst linked = new Set<string>();\n\tfor (const e of edges) {\n\t\tdegree[e.a]++;\n\t\tdegree[e.b]++;\n\t\tlinked.add(`${Math.min(e.a, e.b)},${Math.max(e.a, e.b)}`);\n\t}\n\tconst ends = degree.flatMap((d, v) => (d === 1 ? [v] : [])).sort((a, b) => vertices[a][0] - vertices[b][0]);\n\tconst joined = new Set<number>();\n\tlet lo = 0;\n\tfor (let i = 0; i < ends.length; i++) {\n\t\tconst u = ends[i];\n\t\tif (joined.has(u)) continue;\n\t\tconst pu = vertices[u];\n\t\twhile (lo < ends.length && vertices[ends[lo]][0] < pu[0] - gap) lo++;\n\t\tlet best = -1;\n\t\tlet bestD = Infinity;\n\t\tfor (let j = lo; j < ends.length; j++) {\n\t\t\tconst v = ends[j];\n\t\t\tconst pv = vertices[v];\n\t\t\tif (pv[0] > pu[0] + gap) break;\n\t\t\tif (v === u || joined.has(v) || linked.has(`${Math.min(u, v)},${Math.max(u, v)}`)) continue;\n\t\t\tconst d = dist(pu, pv);\n\t\t\tif (d <= gap && d < bestD) {\n\t\t\t\tbest = v;\n\t\t\t\tbestD = d;\n\t\t\t}\n\t\t}\n\t\tif (best < 0) continue;\n\t\tconst a = Math.min(u, best);\n\t\tconst b = Math.max(u, best);\n\t\tedges.push({ a, b, pts: [vertices[a], vertices[b]], operands: [], src: -1, sa: 0, sb: 1, source: -1 });\n\t\tjoined.add(u);\n\t\tjoined.add(best);\n\t}\n}\n\n// -- cutting edges out of outlines (vectorcraft `cut_out`) ---------------------\n\n/** Whether a contour, closed, encloses an area (rather than retracing itself). */\nexport function contourEnclosesArea(contour: BezierContour): boolean {\n\tconst segs = closeContour(contour, true);\n\tif (!segs.length) return false;\n\tconst b = boundsOf(segs);\n\tconst w = b.maxX - b.minX;\n\tconst h = b.maxY - b.minY;\n\treturn Math.abs(contourSignedArea(segs)) > 1e-9 * (w * w + h * h);\n}\n\n/** The point of a chain nearest `p`, as a parameter `segment + t`, with its distance. */\nfunction nearestU(segs: readonly BezierSegment[], p: Point): { u: number; d: number } | null {\n\tlet best: { u: number; d: number } | null = null;\n\tsegs.forEach((seg, k) => {\n\t\tconst n = segmentNearest(seg, p);\n\t\tif (!best || n.distance < best.d) best = { u: k + n.t, d: n.distance };\n\t});\n\treturn best;\n}\n\n/** The run of a chain from parameter `u0` to `u1` (`u0 < u1`, unrolled past the end of a ring). */\nfunction span(segs: readonly BezierSegment[], u0: number, u1: number): BezierSegment[] {\n\tconst n = segs.length;\n\tif (!n || !Number.isFinite(u0) || !Number.isFinite(u1)) return [];\n\tconst first = Math.max(0, Math.floor(u0));\n\tconst last = Math.min(Math.max(0, Math.ceil(u1)), first + 2 * n);\n\tconst out: BezierSegment[] = [];\n\tfor (let k = first; k < last; k++) {\n\t\tconst t0 = Math.min(1, Math.max(0, u0 - k));\n\t\tconst t1 = Math.min(1, Math.max(0, u1 - k));\n\t\tif (t1 - t0 > 1e-12) out.push(subSegment(segs[k % n], t0, t1));\n\t}\n\treturn out;\n}\n\n/**\n * A run with its end points within `tol` of one of `to` put exactly on it (where the planar map\n * put the junction a piece ends at), the neighbouring handles moving with them.\n */\nfunction snapEnds(segs: BezierSegment[], to: readonly Point[], tol: number): BezierSegment[] {\n\tif (!segs.length) return segs;\n\tconst out = segs.map((s) => [...s]);\n\tconst move = (seg: Point[], end: number, handle: number) => {\n\t\tconst q = to.find((t) => dist(t, seg[end]) <= tol);\n\t\tif (!q) return;\n\t\tconst d: Point = [q[0] - seg[end][0], q[1] - seg[end][1]];\n\t\tseg[end] = q;\n\t\tif (seg.length === 4) seg[handle] = [seg[handle][0] + d[0], seg[handle][1] + d[1]];\n\t};\n\tmove(out[0], 0, 1);\n\tconst last = out[out.length - 1];\n\tmove(last, last.length - 1, last.length - 2);\n\treturn out;\n}\n\n/**\n * `path` without `piece`, a run along one of its subpaths between two points (a Shape Builder\n * edge from `PlanarMap.chains`): a closed subpath opens there, an open one splits in two (or\n * loses an end), and a piece that runs all the way round (`closed`, or ending where it starts)\n * takes its subpath with it. With `fillCloses`, open subpaths that enclose an area run on round\n * an invisible closing edge, as the Shape Builder sees a filled path: a piece may run over it,\n * and it never comes back as a visible segment. Null when the piece does not lie on `path`\n * within `tol`.\n *\n * Ported from vectorcraft-main/crates/pathops/src/planar.rs (`cut_out`, `nearest_u`, `span`,\n * `snap_ends`). MIT OR Apache-2.0, © ArtCraft Team and contributors. See THIRD_PARTY_NOTICES.md.\n */\nexport function cutOutPiece(path: BezierPathInput, piece: { segments: readonly BezierSegment[]; closed?: boolean }, fillCloses: boolean, tol: number): BezierPath | null {\n\tconst segs = piece.segments;\n\tif (!segs.length) return null;\n\t// A point well inside the piece: the middle of its longest segment.\n\tlet mid: Point | null = null;\n\tlet longest = -1;\n\tlet perimeter = 0;\n\tfor (const seg of segs) {\n\t\tconst len = segmentLength(seg);\n\t\tperimeter += len;\n\t\tif (len > longest) {\n\t\t\tlongest = len;\n\t\t\tmid = segmentPoint(seg, 0.5);\n\t\t}\n\t}\n\t// A speck (where paths touch): there is nothing to cut.\n\tif (!mid || perimeter <= tol) return null;\n\tconst a = segs[0][0];\n\tconst lastSeg = segs[segs.length - 1];\n\tconst b = lastSeg[lastSeg.length - 1];\n\tconst whole = !!piece.closed || dist(a, b) <= tol;\n\tconst contours = pathToContours(path);\n\t// Each subpath as the piece may run along it: round its closing edge when it has one.\n\tlet found: { index: number; ring: BezierSegment[]; closed: boolean; um: number; d: number } | null = null;\n\tcontours.forEach((c, index) => {\n\t\tif (!c.segments.length) return;\n\t\tconst closed = c.closed || (fillCloses && contourEnclosesArea(c));\n\t\tconst ring = closeContour(c, closed);\n\t\tconst n = nearestU(ring, mid!);\n\t\tif (n && n.d <= tol && (!found || n.d < found.d)) found = { index, ring, closed, um: n.u, d: n.d };\n\t});\n\tif (!found) return null;\n\tconst { index, ring, closed, um } = found as { index: number; ring: BezierSegment[]; closed: boolean; um: number };\n\tconst len = ring.length;\n\tconst real = closeContour(contours[index]).length;\n\tconst at = (p: Point) => {\n\t\tconst n = nearestU(ring, p);\n\t\treturn n && n.d <= tol ? n.u : null;\n\t};\n\t// The parameter spans of the ring that stay (unrolled past the end of a ring).\n\tlet keep: [number, number][] = [];\n\tif (!whole) {\n\t\tconst ua = at(a);\n\t\tconst ub = at(b);\n\t\tif (ua === null || ub === null) return null;\n\t\tif (closed) {\n\t\t\tconst fwd = (from: number, to: number) => (((to - from) % len) + len) % len;\n\t\t\t// The piece runs from one end through `mid` to the other; what stays runs on round.\n\t\t\tconst [start, end] = fwd(ua, um) <= fwd(ua, ub) ? [ub, ua] : [ua, ub];\n\t\t\tconst rest = fwd(start, end);\n\t\t\tif (rest > 1e-9 && len - rest > 1e-9) {\n\t\t\t\t// Where the ring closes with an invisible edge (`real..len`), what stays stops at it.\n\t\t\t\tconst parts: [number, number][] = real >= len ? [[0, 2 * len]] : [[0, real], [len, len + real]];\n\t\t\t\tkeep = parts.map(([lo, hi]): [number, number] => [Math.max(start, lo), Math.min(start + rest, hi)]).filter(([x, y]) => y - x > 1e-9);\n\t\t\t}\n\t\t} else {\n\t\t\tconst lo = Math.min(ua, ub);\n\t\t\tconst hi = Math.max(ua, ub);\n\t\t\tif (um < lo - 1e-9 || um > hi + 1e-9) return null;\n\t\t\tkeep = ([[0, lo], [hi, len]] as [number, number][]).filter(([x, y]) => y - x > 1e-9);\n\t\t}\n\t}\n\tconst out: BezierContour[] = contours.slice(0, index);\n\tfor (const [u0, u1] of keep) {\n\t\tconst run = span(ring, u0, u1);\n\t\tif (run.length) out.push({ segments: snapEnds(run, [a, b], tol), closed: false });\n\t}\n\tout.push(...contours.slice(index + 1));\n\treturn contoursToPath(out);\n}\n","/**\n * Diagonal hatching: the Shape Builder's mesh highlight over a face.\n *\n * Ported from vectorcraft-main/crates/tools/src/builder.rs (`hatch`). MIT OR Apache-2.0,\n * © ArtCraft Team and contributors. See THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from '../types.js';\nimport { bezierPathBounds, closeContour, flattenChain, pathToContours, type BezierPath, type BezierPathInput } from './bezier.js';\n\nexport interface HatchOptions {\n\t/** Distance between the lines, in the path's units (default 4). */\n\tgap?: number;\n\t/** Most lines drawn: past it the gap widens (default 400). */\n\tmaxLines?: number;\n}\n\n/**\n * 45° hatch lines filling `path` (non-zero and even-odd agree for simple faces; crossings are\n * paired in order, as even-odd does), `gap` apart. Each line is its own `M`/`L` pair, clipped to\n * the path. Empty for a path with no area.\n */\nexport function hatchPath(path: BezierPathInput, options: HatchOptions = {}): BezierPath {\n\tconst out: BezierPath = [];\n\tconst b = bezierPathBounds(path);\n\tif (!b || b.maxX - b.minX <= 0 || b.maxY - b.minY <= 0) return out;\n\tconst want = Math.max(options.gap ?? 4, 1e-3);\n\t// Rotated by -45° the hatch lines are horizontal scanlines.\n\tconst s = Math.SQRT1_2;\n\tconst rot = (p: Point): Point => [(p[0] + p[1]) * s, (p[1] - p[0]) * s];\n\tconst unrot = (x: number, y: number) => ({ x: (x - y) * s, y: (x + y) * s });\n\tconst edges: [Point, Point][] = [];\n\tfor (const contour of pathToContours(path)) {\n\t\tconst pts = flattenChain(closeContour(contour, true), want * 0.1).map(rot);\n\t\tfor (let i = 0; i < pts.length; i++) {\n\t\t\tconst p = pts[i];\n\t\t\tconst q = pts[(i + 1) % pts.length];\n\t\t\tif (p[0] !== q[0] || p[1] !== q[1]) edges.push([p, q]);\n\t\t}\n\t}\n\tlet y0 = Infinity;\n\tlet y1 = -Infinity;\n\tfor (const [p, q] of edges) {\n\t\ty0 = Math.min(y0, p[1], q[1]);\n\t\ty1 = Math.max(y1, p[1], q[1]);\n\t}\n\tif (!(y1 > y0)) return out;\n\tconst gap = Math.max(want, (y1 - y0) / Math.max(1, options.maxLines ?? 400), 1e-6);\n\tconst xs: number[] = [];\n\tfor (let y = Math.floor(y0 / gap) * gap + gap / 2; y < y1; y += gap) {\n\t\txs.length = 0;\n\t\tfor (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]));\n\t\txs.sort((m, n) => m - n);\n\t\tfor (let i = 0; i + 1 < xs.length; i += 2) {\n\t\t\tout.push({ type: 'M', ...unrot(xs[i], y) }, { type: 'L', ...unrot(xs[i + 1], y) });\n\t\t}\n\t}\n\treturn out;\n}\n","/**\n * The Pathfinder panel's dividing operations over a stack of shapes (index 0 at the back):\n * Divide, Trim, Merge, Crop, Outline and Minus Back. (Unite, Minus Front, Intersect and Exclude\n * are `pathBoolean`.) Every result carries the index of the shape whose paint it keeps.\n *\n * The filled areas come from one arrangement of all shapes (`dividePaths`, `pathBooleanSelect`):\n * each area knows the shapes covering it, so an operation is a choice of areas per result.\n * Outline and Divide with cutting lines use the planar map (`PlanarMap`).\n *\n * Ported from vectorcraft-main/crates/pathops/src/pathfinder.rs (`pathfinder`, `PathfinderOp`,\n * `outline_edges`), MIT OR Apache-2.0, © ArtCraft Team and contributors. See\n * THIRD_PARTY_NOTICES.md.\n */\nimport type { PolygonFillRule } from '../polygonBoolean.js';\nimport { pathToContours, contoursToPath, type BezierPath, type BezierPathInput } from './bezier.js';\nimport { dividePaths, pathBooleanSelect, type PathOpsOptions, type PathOpsReport } from './boolean.js';\nimport { PlanarMap } from './planar.js';\n\nexport type PathfinderOperation = 'divide' | 'trim' | 'merge' | 'crop' | 'outline' | 'minusBack';\n\nexport const PATHFINDER_OPERATIONS: readonly PathfinderOperation[] = ['divide', 'trim', 'merge', 'crop', 'outline', 'minusBack'];\n\n/** A shape of the stack. */\nexport interface PathfinderShape {\n\t/** Its outline; open subpaths are closed for filling (unless `line`). */\n\tpath: BezierPathInput;\n\tfillRule?: PolygonFillRule;\n\t/** Identifies its paint: Merge joins touching areas whose shapes share a key (default: the index). */\n\tkey?: string | number;\n\t/**\n\t * An unfilled open path: a cutting edge for Divide, splitting the faces it crosses without\n\t * becoming one (ignored by the other operations).\n\t */\n\tline?: boolean;\n}\n\n/** One result: a filled area (or an open stroke for Outline). */\nexport interface PathfinderPiece {\n\tpath: BezierPath;\n\t/** Index of the shape whose paint it keeps. */\n\tsource: number;\n\t/** Outline edges are open paths (a loop with no junction on it stays closed). */\n\topen?: boolean;\n}\n\nexport interface PathfinderResult extends PathOpsReport {\n\tpieces: PathfinderPiece[];\n}\n\nconst top = (covering: readonly number[]) => covering[covering.length - 1];\n\n/**\n * Runs a Pathfinder operation:\n * - `divide`: every face of the arrangement becomes its own piece, painted like the top-most\n * shape covering it. `line` shapes cut the faces they cross.\n * - `trim`: hidden parts are removed; one piece per shape that stays visible, nothing merged.\n * - `merge`: like Trim, then touching or overlapping visible parts of shapes with the same key\n * become one piece (one per connected area).\n * - `crop`: the top shape crops the others; it is removed, the visible parts of the shapes below\n * inside it stay.\n * - `outline`: every outline is cut where it meets another; the pieces are open paths painted\n * like the shape they bound (the top-most where outlines coincide).\n * - `minusBack`: the top shape minus everything behind it.\n */\nexport function pathfinder(op: PathfinderOperation, shapes: readonly PathfinderShape[], options: PathOpsOptions = {}): PathfinderResult {\n\tconst n = shapes.length;\n\tif (n === 0) return { pieces: [], engine: 'curve' };\n\tconst filled = shapes.filter((s) => !s.line);\n\tconst operands = shapes.map((s) => ({ path: s.path, fillRule: s.fillRule ?? 'nonzero' }));\n\n\tif (op === 'outline') {\n\t\tconst map = PlanarMap.build(\n\t\t\tshapes.map((s) => ({ path: s.line ? s.path : closeAll(s.path), fills: false })),\n\t\t\toptions\n\t\t);\n\t\tif (!map) return { pieces: [], engine: 'curve', error: 'could not map the outlines' };\n\t\tconst pieces = map.chains().map((c) => ({ path: contoursToPath([{ segments: c.segments, closed: c.closed }]), source: c.source, open: !c.closed }));\n\t\treturn { pieces, engine: 'curve' };\n\t}\n\n\tif (op === 'divide') {\n\t\tif (filled.length !== n) {\n\t\t\tconst map = PlanarMap.build(shapes.map((s) => ({ path: s.line ? s.path : closeAll(s.path), fillRule: s.fillRule, fills: !s.line })), options);\n\t\t\tif (!map) return { pieces: [], engine: 'curve', error: 'could not map the shapes' };\n\t\t\tconst pieces: PathfinderPiece[] = [];\n\t\t\tmap.faces.forEach((face, i) => {\n\t\t\t\tconst covering = map.faceSources(i);\n\t\t\t\tif (covering.length) pieces.push({ path: face.path, source: top(covering) });\n\t\t\t});\n\t\t\treturn { pieces, engine: 'curve' };\n\t\t}\n\t\tconst result = dividePaths(operands, options);\n\t\treturn { ...report(result), pieces: result.pieces.map((p) => ({ path: p.path, source: top(p.operands) })) };\n\t}\n\n\t// The filled-area operations ignore cutting lines.\n\tconst index = shapes.flatMap((s, i) => (s.line ? [] : [i]));\n\tconst m = index.length;\n\tif (m === 0) return { pieces: [], engine: 'curve' };\n\tconst ops = index.map((i) => operands[i]);\n\tconst key = (k: number) => shapes[index[k]].key ?? index[k];\n\tconst select = (selectors: ((covering: readonly number[]) => boolean)[]) => pathBooleanSelect(ops, selectors, options);\n\tconst joined = (parts: BezierPath[]) => parts.flat();\n\n\tswitch (op) {\n\t\tcase 'trim': {\n\t\t\tconst result = select(index.map((_, k) => (c) => c.length > 0 && top(c) === k));\n\t\t\tconst pieces = result.parts.flatMap((parts, k) => (parts.length ? [{ path: joined(parts), source: index[k] }] : []));\n\t\t\treturn { ...report(result), pieces };\n\t\t}\n\t\tcase 'merge': {\n\t\t\tconst keys: (string | number)[] = [];\n\t\t\tfor (let k = 0; k < m; k++) if (!keys.includes(key(k))) keys.push(key(k));\n\t\t\tconst result = select(keys.map((kk) => (c) => c.length > 0 && key(top(c)) === kk));\n\t\t\tconst pieces: PathfinderPiece[] = [];\n\t\t\tresult.parts.forEach((parts, j) => {\n\t\t\t\t// Painted like the front-most shape with that key.\n\t\t\t\tlet source = 0;\n\t\t\t\tfor (let k = 0; k < m; k++) if (key(k) === keys[j]) source = index[k];\n\t\t\t\tfor (const part of parts) pieces.push({ path: part, source });\n\t\t\t});\n\t\t\treturn { ...report(result), pieces };\n\t\t}\n\t\tcase 'crop': {\n\t\t\tif (m < 2) return { pieces: [], engine: 'curve' };\n\t\t\tconst front = m - 1;\n\t\t\tconst result = select(\n\t\t\t\tindex.slice(0, front).map((_, k) => (c) => c.length >= 2 && top(c) === front && c[c.length - 2] === k)\n\t\t\t);\n\t\t\tconst pieces = result.parts.flatMap((parts, k) => (parts.length ? [{ path: joined(parts), source: index[k] }] : []));\n\t\t\treturn { ...report(result), pieces };\n\t\t}\n\t\tcase 'minusBack': {\n\t\t\tconst front = m - 1;\n\t\t\tconst result = select([(c) => c.length === 1 && c[0] === front]);\n\t\t\tconst parts = result.parts[0];\n\t\t\treturn { ...report(result), pieces: parts.length ? [{ path: joined(parts), source: index[front] }] : [] };\n\t\t}\n\t}\n\treturn { pieces: [], engine: 'curve' };\n}\n\nfunction report(r: PathOpsReport): PathOpsReport {\n\treturn { engine: r.engine, ...(r.fallbackReason ? { fallbackReason: r.fallbackReason } : {}), ...(r.error ? { error: r.error } : {}) };\n}\n\n/** The path with every subpath closed (as it fills). */\nfunction closeAll(path: BezierPathInput): BezierPath {\n\treturn contoursToPath(pathToContours(path).map((c) => ({ segments: c.segments, closed: true })));\n}\n"]}
|