@pixerate/editor 0.108.0 → 0.110.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/dist/animation/index.cjs +5 -5
- package/dist/animation/index.cjs.map +1 -1
- package/dist/animation/index.js +9 -9
- package/dist/animation-editor/index.cjs +59 -59
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +2 -1
- package/dist/animation-editor/index.d.ts +2 -1
- package/dist/animation-editor/index.js +29 -29
- package/dist/blending-DXl06NSx.d.ts +486 -0
- package/dist/blending-aOE6Y3UE.d.cts +486 -0
- package/dist/canvas/index.cjs +27 -27
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.js +11 -11
- package/dist/{chunk-2FGRTYJN.cjs → chunk-477UVMNR.cjs} +3 -3
- package/dist/{chunk-2FGRTYJN.cjs.map → chunk-477UVMNR.cjs.map} +1 -1
- package/dist/chunk-7VOHOPLW.cjs +1136 -0
- package/dist/chunk-7VOHOPLW.cjs.map +1 -0
- package/dist/{chunk-HUQ7EHVI.js → chunk-BLWCAQCW.js} +723 -1975
- package/dist/chunk-BLWCAQCW.js.map +1 -0
- package/dist/{chunk-SOROZ2BF.js → chunk-DMRSJBJF.js} +1939 -318
- package/dist/chunk-DMRSJBJF.js.map +1 -0
- package/dist/chunk-FTBZPI77.cjs +1 -0
- package/dist/chunk-FTBZPI77.cjs.map +1 -0
- package/dist/{chunk-WMBIJCHA.js → chunk-HNS7UHCM.js} +5 -1
- package/dist/{chunk-WMBIJCHA.js.map → chunk-HNS7UHCM.js.map} +1 -1
- package/dist/chunk-JLYX4V57.cjs +2001 -0
- package/dist/chunk-JLYX4V57.cjs.map +1 -0
- package/dist/chunk-KB2YBCVS.js +2001 -0
- package/dist/chunk-KB2YBCVS.js.map +1 -0
- package/dist/{chunk-G4BIYE6R.js → chunk-LQEEDHOP.js} +40 -2
- package/dist/chunk-LQEEDHOP.js.map +1 -0
- package/dist/{chunk-FRD3FUXI.cjs → chunk-MGL4U5WA.cjs} +2144 -523
- package/dist/chunk-MGL4U5WA.cjs.map +1 -0
- package/dist/{chunk-T3QZ6S47.cjs → chunk-POGBUYWE.cjs} +131 -93
- package/dist/chunk-POGBUYWE.cjs.map +1 -0
- package/dist/chunk-VKK5VS2T.js +1 -0
- package/dist/{chunk-UTEVPQTS.cjs → chunk-W3GXSUSQ.cjs} +746 -1998
- package/dist/chunk-W3GXSUSQ.cjs.map +1 -0
- package/dist/{chunk-SZ6KMZNM.cjs → chunk-XIOMMGPD.cjs} +6 -2
- package/dist/chunk-XIOMMGPD.cjs.map +1 -0
- package/dist/{chunk-JK6M6V7G.js → chunk-Z3YWVUWT.js} +4 -4
- package/dist/{chunk-3PHA3UKI.js → chunk-Z4BC6M74.js} +523 -121
- package/dist/chunk-Z4BC6M74.js.map +1 -0
- package/dist/{export-a9bnF7Cl.d.ts → export-CO2ryu3I.d.ts} +419 -18
- package/dist/{export-JGWQEPJn.d.cts → export-DRiLzP_j.d.cts} +419 -18
- package/dist/image-editor/index.cjs +1665 -1113
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +78 -14
- package/dist/image-editor/index.d.ts +78 -14
- package/dist/image-editor/index.js +843 -291
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint/index.cjs +28 -4
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +3 -89
- package/dist/paint/index.d.ts +3 -89
- package/dist/paint/index.js +27 -3
- package/dist/paint-editor/index.cjs +1056 -287
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +304 -22
- package/dist/paint-editor/index.d.ts +304 -22
- package/dist/paint-editor/index.js +852 -83
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/raster/index.cjs +7 -3
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +3 -129
- package/dist/raster/index.d.ts +3 -129
- package/dist/raster/index.js +6 -2
- package/dist/{compositor-D92URMlm.d.cts → render-BEriFuqK.d.cts} +136 -2
- package/dist/{compositor-Kt_M4ruo.d.ts → render-tjzKaik-.d.ts} +136 -2
- package/dist/{surface-CkKOHGPm.d.cts → surface-C8RRg_Bh.d.cts} +109 -1
- package/dist/{surface-CkKOHGPm.d.ts → surface-C8RRg_Bh.d.ts} +109 -1
- package/dist/{svg-SseJTIrA.d.ts → svg-D_cfkhlB.d.cts} +3 -629
- package/dist/{svg-ClVIG4T7.d.cts → svg-wIaKLrU0.d.ts} +3 -629
- package/dist/types-DtL_qLFz.d.ts +630 -0
- package/dist/types-DyQq-WPY.d.cts +630 -0
- package/dist/vector/index.cjs +563 -563
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +4 -2
- package/dist/vector/index.d.ts +4 -2
- package/dist/vector/index.js +69 -69
- package/dist/video-editor/index.cjs +3 -3
- package/dist/video-editor/index.js +2 -2
- package/package.json +1 -1
- package/skills/pixerate-paint-editor/SKILL.md +49 -13
- package/dist/chunk-3PHA3UKI.js.map +0 -1
- package/dist/chunk-6BU5WU4R.cjs +0 -1
- package/dist/chunk-6BU5WU4R.cjs.map +0 -1
- package/dist/chunk-DWGZUSES.cjs +0 -694
- package/dist/chunk-DWGZUSES.cjs.map +0 -1
- package/dist/chunk-FRD3FUXI.cjs.map +0 -1
- package/dist/chunk-G4BIYE6R.js.map +0 -1
- package/dist/chunk-HUFQD4CB.cjs +0 -734
- package/dist/chunk-HUFQD4CB.cjs.map +0 -1
- package/dist/chunk-HUQ7EHVI.js.map +0 -1
- package/dist/chunk-KKREERP2.js +0 -694
- package/dist/chunk-KKREERP2.js.map +0 -1
- package/dist/chunk-SOROZ2BF.js.map +0 -1
- package/dist/chunk-SZ6KMZNM.cjs.map +0 -1
- package/dist/chunk-T3QZ6S47.cjs.map +0 -1
- package/dist/chunk-UTEVPQTS.cjs.map +0 -1
- package/dist/chunk-XYBJKUAU.js +0 -1
- package/dist/mixer-5ziHU8OS.d.cts +0 -282
- package/dist/mixer-DNqiYXYQ.d.ts +0 -282
- /package/dist/{chunk-XYBJKUAU.js.map → chunk-VKK5VS2T.js.map} +0 -0
- /package/dist/{chunk-JK6M6V7G.js.map → chunk-Z3YWVUWT.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-UTEVPQTS.cjs","../src/vector/types.ts","../src/vector/gradient.ts","../src/vector/corners.ts","../src/vector/width.ts","../src/vector/varstroke.ts","../src/vector/stroke.ts","../src/vector/paintLayers.ts","../src/vector/pathMap.ts","../src/vector/pathCut.ts","../src/vector/filters.ts","../src/vector/mask.ts","../src/vector/warp.ts","../src/vector/appearance.ts","../src/vector/geometry.ts","../src/vector/pathEffects.ts","../src/vector/blend.ts","../src/vector/gradientStroke.ts","../src/vector/svg.ts"],"names":["v","at","tangent","plain","s","start","frame","along","mirrored","capped","clean","mul","cross","pt","dist","next","toVec","lerp","out","invert","EPS","len","KAPPA","left","c","fmt","escapeAttr","w","emit","width","nodes"],"mappings":"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mC,MAAA;AACP,MAAA;AACrB,MAAA;AACtB,IAAA;AACqD,IAAA;AACd,IAAA;AACnC,IAAA;AACiD,IAAA;AACtB,IAAA;AACD,MAAA;AACV,MAAA;AACpB,IAAA;AACkD,IAAA;AACH,IAAA;AAChD,EAAA;AAEgG,EAAA;AACpE,IAAA;AACwB,IAAA;AACnB,IAAA;AACoB,IAAA;AACH,IAAA;AACN,IAAA;AACT,IAAA;AACrB,IAAA;AACqC,MAAA;AACG,MAAA;AACjB,MAAA;AACS,MAAA;AACR,MAAA;AACrC,IAAA;AAEyD,IAAA;AAC3B,IAAA;AACgG,IAAA;AACxF,IAAA;AACG,MAAA;AACjB,MAAA;AAChB,MAAA;AACR,IAAA;AACsC,IAAA;AACE,MAAA;AAChB,MAAA;AAChB,MAAA;AACR,IAAA;AACsC,IAAA;AACK,MAAA;AACnB,MAAA;AAChB,MAAA;AACR,IAAA;AACsC,IAAA;AACI,MAAA;AAClB,MAAA;AAChB,MAAA;AACR,IAAA;AACkD,IAAA;AACf,MAAA;AACL,MAAA;AACqC,MAAA;AAC/B,MAAA;AACV,MAAA;AACX,MAAA;AACP,MAAA;AACR,IAAA;AAC8B,IAAA;AACY,IAAA;AAC7B,IAAA;AACZ,MAAA;AACC,QAAA;AACgB,UAAA;AACT,UAAA;AACH,UAAA;AACH,UAAA;AAC6C,UAAA;AACvB,UAAA;AACN,UAAA;AACjB,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACa,IAAA;AACN,IAAA;AACR,EAAA;AAEqC,EAAA;AACU,EAAA;AACjB,EAAA;AAC/B;AlBq/JyD;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;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;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;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-UTEVPQTS.cjs","sourcesContent":[null,"import type { CanvasGradientDef } from '../canvas/gradient.js';\nimport type { WarpMesh, WarpStyle } from '../geom/types.js';\n\n/** An affine matrix `[a, b, c, d, e, f]`: `x' = a·x + c·y + e`, `y' = b·x + d·y + f` (SVG order). */\nexport type VectorMatrix = readonly [number, number, number, number, number, number];\n\n/** A point in document coordinates. */\nexport interface Vec2 {\n\tx: number;\n\ty: number;\n}\n\n/**\n * How an anchor's handles relate:\n * - `corner`: handles move independently (or are absent).\n * - `smooth`: handles stay on one line through the anchor, lengths independent.\n * - `symmetric`: handles stay on one line with equal lengths.\n */\nexport type VectorAnchorType = 'corner' | 'smooth' | 'symmetric';\n\n/**\n * Live corner shapes: `round` (an arc tangent to both sides), `inverted` (an arc centred on\n * the corner, cut into the shape) and `chamfer` (a straight bevel).\n */\nexport type VectorCornerKind = 'round' | 'inverted' | 'chamfer';\n\nexport const VECTOR_CORNER_KINDS: readonly VectorCornerKind[] = ['round', 'inverted', 'chamfer'];\n\n/**\n * A live corner: drawn rounded (or cut) at render, hit-test, bounds, boolean, outline and export\n * time while the stored anchor stays sharp. The radius shrinks to fit when the sides are short:\n * the cut reaches at most halfway along each side.\n */\nexport interface VectorCorner {\n\tradius: number;\n\t/** Default `round`. */\n\tkind?: VectorCornerKind;\n}\n\n/**\n * One anchor of a subpath. Handles are absolute document positions; a missing (or `null`)\n * handle sits on the anchor, so a segment with no handles at either end is a straight line.\n */\nexport interface VectorAnchor {\n\tx: number;\n\ty: number;\n\t/** Control point of the segment arriving at this anchor. */\n\tin?: Vec2 | null;\n\t/** Control point of the segment leaving this anchor. */\n\tout?: Vec2 | null;\n\t/** Default `corner`. */\n\ttype?: VectorAnchorType;\n\t/** Live corner radius. Ignored on smooth / symmetric anchors and the ends of open subpaths. */\n\tcorner?: VectorCorner | null;\n}\n\n/** A chain of anchors joined by line or cubic Bézier segments. */\nexport interface VectorSubpath {\n\tanchors: VectorAnchor[];\n\t/** Joins the last anchor back to the first. */\n\tclosed: boolean;\n}\n\n/**\n * Gradient styles: `linear` (start → end), `radial` (centred on the start, reaching the end;\n * focal point and aspect), `conic` (an angle sweep around the start, from the end's direction\n * towards the aspect handle's: clockwise on screen), `reflected` (linear mirrored about the\n * start) and `diamond` (`|u| + |v|` in the gradient's frame: a rhombus through the end and the\n * aspect handle).\n */\nexport type VectorGradientType = 'linear' | 'radial' | 'conic' | 'reflected' | 'diamond';\nexport const VECTOR_GRADIENT_TYPES: readonly VectorGradientType[] = ['linear', 'radial', 'conic', 'reflected', 'diamond'];\n\n/** What a gradient paints past its ends: the end colours, mirrored periods or repeated periods. */\nexport type VectorGradientSpread = 'pad' | 'reflect' | 'repeat';\nexport const VECTOR_GRADIENT_SPREADS: readonly VectorGradientSpread[] = ['pad', 'reflect', 'repeat'];\n\n/** Where colours mix between stops: sRGB (SVG's own), linear light or Oklab. */\nexport type VectorGradientInterpolation = 'srgb' | 'linear' | 'oklab';\nexport const VECTOR_GRADIENT_INTERPOLATIONS: readonly VectorGradientInterpolation[] = ['srgb', 'linear', 'oklab'];\n\n/**\n * The space a gradient's points are in: fractions of the shape's bounding box (SVG's\n * `objectBoundingBox`, the default) or the node's own coordinates (`userSpaceOnUse`).\n */\nexport type VectorGradientUnits = 'objectBoundingBox' | 'userSpaceOnUse';\n\n/**\n * How a gradient lies on a stroke: `within` (laid over the stroke's area like a fill), `along`\n * (t runs from 0 at the start of each subpath to 1 at its end) or `across` (from the stroke's\n * left edge to its right edge, looking along the path).\n */\nexport type VectorStrokeGradientMode = 'within' | 'along' | 'across';\nexport const VECTOR_STROKE_GRADIENT_MODES: readonly VectorStrokeGradientMode[] = ['within', 'along', 'across'];\n\n/** A colour stop. `opacity` multiplies the colour's own alpha. */\nexport interface VectorGradientStop {\n\t/** 0–1. */\n\toffset: number;\n\tcolor: string;\n\t/** 0–1, default 1. */\n\topacity?: number;\n\t/**\n\t * Where, between this stop and the next (0–1 of the way), the colour is half-way between\n\t * them. Default 0.5 (an even mix).\n\t */\n\tmidpoint?: number;\n}\n\n/**\n * A gradient paint. `CanvasGradientDef` (the canvas package's linear `angle` / radial\n * `cx, cy, r` gradients) is a valid value: those fields are the shorthand form. Explicit\n * geometry (`start` / `end`, in `units`) wins over the shorthand.\n */\nexport interface VectorGradient {\n\ttype: VectorGradientType;\n\tstops: VectorGradientStop[];\n\t/**\n\t * Shorthand direction in degrees, CSS style (0 points up, clockwise): a linear or reflected\n\t * gradient's direction across the box (default 180), a conic gradient's start angle or a\n\t * diamond's turn (default 90).\n\t */\n\tangle?: number;\n\t/** Shorthand centre and radius of radial, conic and diamond gradients (fractions of the box, default 0.5). */\n\tcx?: number;\n\tcy?: number;\n\tr?: number;\n\t/** Where t = 0: a linear gradient's start, the centre of the other styles. */\n\tstart?: Vec2;\n\t/** Where t = 1 along the gradient's main axis (a radial gradient's radius). */\n\tend?: Vec2;\n\t/**\n\t * The end of the secondary axis (radial, conic and diamond): its distance from `start` over\n\t * the main axis' length is the aspect ratio. Absent: square to the main axis, as long.\n\t */\n\twidth?: Vec2 | null;\n\t/** A radial gradient's focal point (where t = 0); absent: the centre. */\n\tfocal?: Vec2 | null;\n\t/** Radius of the focal circle as a fraction of the radius (SVG `fr`, default 0). */\n\tfocalRadius?: number;\n\t/** Space of `start`, `end`, `width` and `focal` (default `objectBoundingBox`). */\n\tunits?: VectorGradientUnits;\n\t/** Default `pad`. */\n\tspread?: VectorGradientSpread;\n\t/** Default `srgb`. */\n\tinterpolation?: VectorGradientInterpolation;\n\t/**\n\t * 0–1: how far the colours follow a smooth spline through the stops instead of straight\n\t * lines (Photoshop's Smoothness). Default 0.\n\t */\n\tsmoothness?: number;\n\t/** On a stroke: how the gradient lies on it (default `within`). Ignored on fills. */\n\tstrokeMode?: VectorStrokeGradientMode;\n}\n\n/** A fill or stroke: any CSS colour, a gradient, or `null` / `'none'` for no paint. */\nexport type VectorPaint = string | VectorGradient | CanvasGradientDef | null;\n\nexport type VectorFillRule = 'nonzero' | 'evenodd';\nexport type VectorLineCap = 'butt' | 'round' | 'square';\nexport type VectorLineJoin = 'miter' | 'round' | 'bevel';\n/**\n * Where the stroke sits on the outline: centred on it, inside the shape or outside it. Only\n * shapes whose subpaths are all closed have an inside; open paths always stroke centred.\n */\nexport type VectorStrokeAlign = 'center' | 'inside' | 'outside';\n/** Arrowheads and end markers for the ends of open paths. */\nexport type VectorArrowhead = 'triangle' | 'arrow' | 'open' | 'circle' | 'square' | 'bar';\nexport const VECTOR_ARROWHEADS: readonly VectorArrowhead[] = ['triangle', 'arrow', 'open', 'circle', 'square', 'bar'];\n/**\n * `extend`: the head's tip sits past the end point and the path keeps its length; `tip`: the\n * tip sits on the end point and the stroke is shortened under the head.\n */\nexport type VectorArrowAlign = 'extend' | 'tip';\n\n/**\n * One point of a variable-width stroke profile. `t` is the position along the subpath by\n * length (0 at its start, 1 at its end); `left` and `right` are how far the stroke reaches on\n * each side, as multiples of half the stroke width (looking along the path on screen, y down).\n * A uniform stroke has 1 on both sides. Two points at the same `t` make a step.\n */\nexport interface VectorWidthPoint {\n\tt: number;\n\tleft: number;\n\tright: number;\n}\n\n/**\n * Blend modes of paint layers and nodes: the set SVG and CSS `mix-blend-mode` draw, named as the\n * image editor names them (`@pixerate/editor/image-editor` `BlendMode`). `linear-dodge` draws as\n * CSS `plus-lighter`. The image editor's other modes (linear burn, vivid light, pigment, …) have\n * no SVG equivalent and are not offered.\n */\nexport type VectorBlendMode =\n\t| 'normal'\n\t| 'darken'\n\t| 'multiply'\n\t| 'color-burn'\n\t| 'lighten'\n\t| 'screen'\n\t| 'color-dodge'\n\t| 'linear-dodge'\n\t| 'overlay'\n\t| 'soft-light'\n\t| 'hard-light'\n\t| 'difference'\n\t| 'exclusion'\n\t| 'hue'\n\t| 'saturation'\n\t| 'color'\n\t| 'luminosity';\n/** Every vector blend mode, in the image editor's (Photoshop's) menu order. */\nexport const VECTOR_BLEND_MODES: readonly VectorBlendMode[] = [\n\t'normal',\n\t'darken',\n\t'multiply',\n\t'color-burn',\n\t'lighten',\n\t'screen',\n\t'color-dodge',\n\t'linear-dodge',\n\t'overlay',\n\t'soft-light',\n\t'hard-light',\n\t'difference',\n\t'exclusion',\n\t'hue',\n\t'saturation',\n\t'color',\n\t'luminosity'\n];\n\n/** One fill of a path's fill stack (`VectorStyle.fills`). */\nexport interface VectorPaintLayer {\n\t/** A colour, a gradient, or `null` for none. */\n\tpaint: VectorPaint;\n\t/** 0–1, default 1. Multiplies the paint's own alpha. */\n\topacity?: number;\n\t/** How the layer mixes with what is under it (the layers below, then the backdrop). Default `normal`. */\n\tblendMode?: VectorBlendMode;\n\t/** Hidden layers are kept but not drawn or hit. Default true. */\n\tvisible?: boolean;\n}\n\n/** The settings each stroke layer has of its own (the stroke shorthand fields of `VectorStyle`). */\nexport type VectorStrokeSettings = Pick<\n\tVectorStyle,\n\t| 'strokeWidth'\n\t| 'strokeLinecap'\n\t| 'strokeLinejoin'\n\t| 'strokeDasharray'\n\t| 'strokeAlign'\n\t| 'startArrow'\n\t| 'endArrow'\n\t| 'arrowAlign'\n\t| 'startArrowScale'\n\t| 'endArrowScale'\n\t| 'strokeWidthProfile'\n>;\n\n/**\n * What a stroke layer draws. Only `paint` (a paint along the outline, the default) exists today.\n * Other kinds are the extension point for strokes drawn some other way, such as a raster\n * brushstroke from `@pixerate/editor/paint` laid along the path: a layer with an unknown kind is\n * kept through edits, load and SVG round trips, listed to agents, and skipped when drawing and\n * hit-testing.\n */\nexport type VectorStrokeLayerKind = 'paint';\n\n/** One stroke of a path's stroke stack (`VectorStyle.strokes`): a paint plus its own stroke settings. */\nexport interface VectorStrokeLayer extends VectorPaintLayer, VectorStrokeSettings {\n\t/** Default `paint`. See `VectorStrokeLayerKind`. */\n\tkind?: VectorStrokeLayerKind;\n}\n\n/** Paint and stroke settings of a path. */\nexport interface VectorStyle {\n\tfill?: VectorPaint;\n\tstroke?: VectorPaint;\n\t/** Default 1. */\n\tstrokeWidth?: number;\n\tstrokeLinecap?: VectorLineCap;\n\tstrokeLinejoin?: VectorLineJoin;\n\tstrokeDasharray?: number[];\n\t/** Default `nonzero`. */\n\tfillRule?: VectorFillRule;\n\t/** Default `center`. */\n\tstrokeAlign?: VectorStrokeAlign;\n\t/** Head at the start of the first subpath when it is open, or none. */\n\tstartArrow?: VectorArrowhead | null;\n\t/** Head at the end of the last subpath when it is open, or none. */\n\tendArrow?: VectorArrowhead | null;\n\t/** Default `extend`. */\n\tarrowAlign?: VectorArrowAlign;\n\t/** Size of the start head as a multiple of the stroke width (default 1: a head 4 widths long). */\n\tstartArrowScale?: number;\n\t/** Size of the end head (default 1). */\n\tendArrowScale?: number;\n\t/**\n\t * Variable width: side scales along each subpath, interpolated with a monotone cubic (see\n\t * `widthProfileAt`). Absent, empty or `null` strokes at a uniform width. A profiled stroke is\n\t * drawn as its filled outline.\n\t */\n\tstrokeWidthProfile?: VectorWidthPoint[] | null;\n\t/**\n\t * The fill stack, bottom to top. Absent: `fill` alone is the stack (one layer, or none without\n\t * a paint). Present: it is the truth, and `fill` mirrors its top layer's paint. The shorthand\n\t * stays writable: setting `fill` to a paint repaints the top layer, `null` clears the stack.\n\t * Stacks of one plain layer (full opacity, `normal`, visible) are stored as the shorthand.\n\t */\n\tfills?: VectorPaintLayer[] | null;\n\t/**\n\t * The stroke stack, bottom to top, drawn over every fill. Like `fills`: absent, `stroke` and the\n\t * stroke settings above are the one stroke; present, they mirror its top layer, and writing them\n\t * edits that layer (`stroke: null` clears the stack).\n\t */\n\tstrokes?: VectorStrokeLayer[] | null;\n}\n\ninterface VectorNodeBase {\n\tid: string;\n\tname?: string;\n\t/** Default true. */\n\tvisible?: boolean;\n\t/** Locked nodes are drawn but cannot be picked or edited on the canvas. */\n\tlocked?: boolean;\n\t/** 0–1, default 1. */\n\topacity?: number;\n\t/**\n\t * Placement in the parent's space: a rigid matrix (rotation + translation). Anchors and\n\t * children are in the node's own space. Omitted for unrotated nodes, whose positions are\n\t * stored directly. Scale and skew are never stored here — `transformVectorNode` bakes\n\t * them into the geometry, so stroke widths and the rotated selection box stay exact.\n\t */\n\ttransform?: VectorMatrix;\n\t/**\n\t * An in-between a blend group generated from its keys. Steps are rebuilt whenever a key or\n\t * the blend options change, and are locked (clicks select the blend group).\n\t */\n\tblendStep?: boolean;\n\t/**\n\t * Live path effects, applied top to bottom: each acts on every path above it (the node's own\n\t * outline, or a group's children, then the result of the effects before it), and effects after a\n\t * repeater act on its copies. The stored anchors and children never change.\n\t */\n\teffects?: VectorEffect[] | null;\n\t/**\n\t * As an operand of a live boolean group: how it combines with the result of the operands below\n\t * it. Absent: the group's `liveBoolean` operation.\n\t */\n\tbooleanOperation?: VectorBooleanOperation | null;\n\t/** How the whole node (all its paint, filters included) mixes with what is under it. Default `normal`. */\n\tblendMode?: VectorBlendMode;\n\t/**\n\t * Visual filters (drop and inner shadows, glows, blurs, feather), applied to everything the\n\t * node draws. Unlike path `effects` they never change geometry: bounds, hit-testing and\n\t * booleans ignore them.\n\t */\n\tfilters?: VectorFilter[] | null;\n}\n\n/** A shape made of one or more subpaths (holes and compound shapes included). */\nexport interface VectorPathNode extends VectorNodeBase, VectorStyle {\n\ttype: 'path';\n\tsubpaths: VectorSubpath[];\n}\n\n/**\n * How a blend spaces its in-betweens: a number of `steps` between each pair of keys, a fixed\n * `distance` between steps along the spine, or `smoothColor` (as many steps as the colours\n * need for a smooth ramp; by distance when the colours match).\n */\nexport type VectorBlendSpacing = 'steps' | 'distance' | 'smoothColor';\nexport const VECTOR_BLEND_SPACINGS: readonly VectorBlendSpacing[] = ['steps', 'distance', 'smoothColor'];\n\n/** Settings of a blend. */\nexport interface VectorBlendOptions {\n\t/** Default `steps`. */\n\tspacing?: VectorBlendSpacing;\n\t/** Steps between each pair of keys with `steps` spacing (default 5). */\n\tsteps?: number;\n\t/** Distance between steps along the spine with `distance` spacing (default 20). */\n\tdistance?: number;\n\t/** Turn the steps with the spine's direction (default `false`: they keep the keys' orientation). */\n\torientToSpine?: boolean;\n\t/** Move anchors in straight lines instead of the rotation-aware morph (default `false`). */\n\tlinear?: boolean;\n}\n\n/** A live blend: the group's keys plus how the in-betweens are made and laid out. */\nexport interface VectorBlend extends VectorBlendOptions {\n\t/**\n\t * A replaced spine in the group's space: the steps follow it instead of the straight lines\n\t * between the keys' centres (the keys sit along it, spread evenly by arc length).\n\t */\n\tspine?: VectorSubpath | null;\n\t/** The anchor of each key's first subpath its blend starts from, by key id (the Blend tool's clicks). */\n\tstarts?: Record<string, number>;\n}\n\n/**\n * How a mask group's mask (its first, bottom child) hides the rest: `clip` (a clipping mask: the\n * mask's outline, fully opaque; its paint is not drawn), `alpha` (an opacity mask by the mask's\n * drawn alpha: soft edges, gradients and opacity count) or `luminance` (by its brightness times\n * its alpha: white shows, black hides).\n */\nexport type VectorMaskType = 'clip' | 'alpha' | 'luminance';\nexport const VECTOR_MASK_TYPES: readonly VectorMaskType[] = ['clip', 'alpha', 'luminance'];\n\n/** A group's mask settings (`VectorGroupNode.mask`). */\nexport interface VectorMask {\n\ttype: VectorMaskType;\n\t/** Show the contents where the mask hides them and hide them where it shows them. Default false. */\n\tinvert?: boolean;\n}\n\n/** A group of nodes that select and transform together. */\nexport interface VectorGroupNode extends VectorNodeBase {\n\ttype: 'group';\n\tchildren: VectorNode[];\n\t/**\n\t * Makes the group a live blend: its children that are not `blendStep`s are the keys, in\n\t * stacking order, and the steps between them are regenerated whenever a key changes.\n\t */\n\tblend?: VectorBlend | null;\n\t/**\n\t * Makes the group a live boolean: its children stay editable operands, folded bottom to top\n\t * (each by its own `booleanOperation` or this one), and the group draws as the result in the\n\t * bottom operand's style.\n\t */\n\tliveBoolean?: VectorBooleanOperation | null;\n\t/**\n\t * Makes the group a mask group: its first (bottom) child is the mask and the children above it\n\t * show only where the mask lets them (see `VectorMaskType`). Hit-testing, bounds, snapping and\n\t * booleans use the masked result. A hidden mask child turns the mask off (the contents draw\n\t * whole). The group's own opacity, blend mode and filters apply to the masked result, and its\n\t * path effects to the contents (not the mask). Ignored on blends and live booleans.\n\t */\n\tmask?: VectorMask | null;\n}\n\nexport type VectorNode = VectorPathNode | VectorGroupNode;\n\n/** A vector drawing: an artboard and its nodes, bottom to top. */\nexport interface VectorDocument {\n\twidth: number;\n\theight: number;\n\t/** Artboard colour (default: transparent). */\n\tbackground?: string;\n\tnodes: VectorNode[];\n}\n\n/** An axis-aligned rectangle. */\nexport interface VectorBounds {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n}\n\n/** Identifies one anchor of a path node. */\nexport interface VectorAnchorRef {\n\tnodeId: string;\n\tsubpath: number;\n\tindex: number;\n}\n\n/** Identifies one handle of an anchor. */\nexport interface VectorHandleRef extends VectorAnchorRef {\n\thandle: 'in' | 'out';\n}\n\n/**\n * Canvas tools. `direct` edits anchors, handles and segments; the shape tools drag out a new\n * shape; `scissors` cuts a path at a point, `knife` cuts filled shapes along a freehand stroke,\n * `eraser` rubs out the area under one and `width` drags a stroke's width at a point.\n * `shapeBuilder` merges (Alt: deletes) the faces the selected shapes make, and `paintBucket`\n * fills the region the visible outlines enclose around a click with a new shape. `blend`\n * blends the shape clicked first into the one clicked next (clicking an anchor of a closed path\n * starts the blend there). `envelope` edits the mesh of the selection's envelope: drag its\n * anchors and handles, or inside a patch to pull the surface.\n */\nexport type VectorTool =\n\t| 'select'\n\t| 'direct'\n\t| 'pen'\n\t| 'pencil'\n\t| 'rect'\n\t| 'ellipse'\n\t| 'polygon'\n\t| 'star'\n\t| 'line'\n\t| 'scissors'\n\t| 'knife'\n\t| 'eraser'\n\t| 'width'\n\t| 'shapeBuilder'\n\t| 'paintBucket'\n\t| 'blend'\n\t| 'gradient'\n\t| 'envelope';\n\nexport const VECTOR_TOOLS: readonly VectorTool[] = [\n\t'select',\n\t'direct',\n\t'pen',\n\t'pencil',\n\t'rect',\n\t'ellipse',\n\t'polygon',\n\t'star',\n\t'line',\n\t'scissors',\n\t'knife',\n\t'eraser',\n\t'width',\n\t'shapeBuilder',\n\t'paintBucket',\n\t'blend',\n\t'gradient',\n\t'envelope'\n];\n\n/** Keyboard modifiers held during a pointer event. */\nexport interface VectorModifiers {\n\tshift?: boolean;\n\talt?: boolean;\n\t/** Cmd on macOS, Ctrl elsewhere. */\n\tmod?: boolean;\n}\n\n/** The eight resize handles of a selection box and the rotation handle. */\nexport type VectorTransformHandle = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'rotate';\n\nexport const VECTOR_TRANSFORM_HANDLES: readonly VectorTransformHandle[] = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'rotate'];\n\n/** Pan and zoom of the canvas: `screen = document × zoom + (x, y)`. */\nexport interface VectorViewport {\n\t/** Screen position of the document origin, in screen pixels. */\n\tx: number;\n\ty: number;\n\tzoom: number;\n}\n\n/** What the next shape and pen path are drawn with. */\nexport interface VectorDefaults extends VectorStyle {\n\t/** Sides of the polygon tool (default 6). */\n\tpolygonSides?: number;\n\t/** Points of the star tool (default 5). */\n\tstarPoints?: number;\n\t/** Inner radius of the star tool as a fraction of the outer one (default 0.5). */\n\tstarInnerRatio?: number;\n\t/** Live corner radius the rectangle, polygon and star tools give their corners (default 0). */\n\tcornerRadius?: number;\n\t/** Corner kind of those tools (default `round`). */\n\tcornerKind?: VectorCornerKind;\n}\n\nexport type VectorAlign = 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom';\nexport type VectorDistribute = 'horizontal' | 'vertical';\nexport type VectorBooleanOperation = 'union' | 'subtract' | 'intersect' | 'exclude';\n/**\n * Pathfinder operations: `divide` (every face its own shape), `trim` (hidden parts removed),\n * `merge` (Trim, then touching parts of one fill joined), `crop` (the top shape crops the others),\n * `outline` (outlines cut into open strokes where they meet) and `minusBack` (the top shape minus\n * everything below it).\n */\nexport type VectorPathfinderOperation = 'divide' | 'trim' | 'merge' | 'crop' | 'outline' | 'minusBack';\nexport const VECTOR_PATHFINDER_OPERATIONS: readonly VectorPathfinderOperation[] = ['divide', 'trim', 'merge', 'crop', 'outline', 'minusBack'];\nexport type VectorArrange = 'front' | 'forward' | 'backward' | 'back';\n/** Average: line anchors up on a horizontal line (same y), a vertical one (same x), or both. */\nexport type VectorAverageAxis = 'horizontal' | 'vertical' | 'both';\n\n/**\n * Live path effects (After Effects' path operators): `trim` (Trim Paths), `roundCorners`,\n * `puckerBloat`, `twist`, `zigzag`, `wiggle` (Wiggle Paths), `offset` (Offset Paths), `merge`\n * (Merge Paths), `repeater` and `envelope` (an envelope distortion: a warp preset or a mesh).\n */\nexport type VectorEffectType = 'trim' | 'roundCorners' | 'puckerBloat' | 'twist' | 'zigzag' | 'wiggle' | 'offset' | 'merge' | 'repeater' | 'envelope';\nexport const VECTOR_EFFECT_TYPES: readonly VectorEffectType[] = ['trim', 'roundCorners', 'puckerBloat', 'twist', 'zigzag', 'wiggle', 'offset', 'merge', 'repeater', 'envelope'];\n\n/** Warp presets (Photoshop's fifteen, in menu order); shared with the image editor (`src/geom`). */\nexport type VectorWarpStyle = WarpStyle;\n\n/**\n * A grid of bicubic Bézier patches mapping a source box onto the plane. `us` / `vs` are the patch\n * boundaries as fractions of the box (increasing, first 0, last 1); `points` is the\n * `(3·(us.length − 1) + 1) × (3·(vs.length − 1) + 1)` control grid, row by row, in the node's own\n * space. Neighbouring patches share their edge row: anchors sit where both indices are multiples\n * of 3, handles where one is. Shared with the image editor (`src/geom`).\n */\nexport type VectorWarpMesh = WarpMesh;\n\n/** Merge Paths modes: a compound path (no boolean), union, the first minus the others, intersection or exclusion. */\nexport type VectorMergeMode = 'merge' | 'add' | 'subtract' | 'intersect' | 'exclude';\nexport const VECTOR_MERGE_MODES: readonly VectorMergeMode[] = ['merge', 'add', 'subtract', 'intersect', 'exclude'];\n\n/** Parameters of each effect type. Lengths are in the node's own units, angles in degrees, percentages 0–100. */\nexport interface VectorEffectParamsMap {\n\ttrim: {\n\t\t/** Start and end of the kept part, percent of the length (swapped when start > end). */\n\t\tstart: number;\n\t\tend: number;\n\t\t/** Shifts both, in degrees (360° = once round). */\n\t\toffset: number;\n\t\t/**\n\t\t * `simultaneous`: every path trimmed by the same fractions of its own length;\n\t\t * `individual`: the paths laid end to end in stacking order and trimmed as one length.\n\t\t */\n\t\tmode: 'simultaneous' | 'individual';\n\t};\n\troundCorners: { radius: number };\n\t/** Percent, −100 (pucker: spiky) to 100 (bloat: bulging). */\n\tpuckerBloat: { amount: number };\n\ttwist: {\n\t\t/** Turn at the farthest point from the centre (none at the centre). */\n\t\tangle: number;\n\t\t/** The centre as an offset from the middle of the paths' bounds. */\n\t\tcenter: Vec2;\n\t};\n\tzigzag: {\n\t\t/** How far the points move to either side. */\n\t\tsize: number;\n\t\t/** Ridges per segment. */\n\t\tridges: number;\n\t\t/** Rounded waves instead of sharp zig zags. */\n\t\tsmooth: boolean;\n\t};\n\twiggle: {\n\t\t/** Largest displacement. */\n\t\tsize: number;\n\t\t/** Points added per segment. */\n\t\tdetail: number;\n\t\tsmooth: boolean;\n\t\t/** Wiggles per second. */\n\t\tspeed: number;\n\t\t/** Percent: how much neighbouring points move together. */\n\t\tcorrelation: number;\n\t\t/** Degrees (360° = one wiggle). */\n\t\tphase: number;\n\t\t/** The same seed (and time) always gives the same wiggle. */\n\t\tseed: number;\n\t\t/** Seconds: where along the wiggle's motion it is drawn. */\n\t\ttime: number;\n\t};\n\toffset: {\n\t\t/** Grows (positive) or shrinks (negative) the shapes. */\n\t\tamount: number;\n\t\tjoin: VectorLineJoin;\n\t\tmiterLimit: number;\n\t\t/** Copies, copy i offset by `amount · (i + 1 + copyOffset)`. */\n\t\tcopies: number;\n\t\tcopyOffset: number;\n\t};\n\tmerge: { mode: VectorMergeMode };\n\trepeater: {\n\t\t/** Copies, the original included; a fraction fades the last copy. */\n\t\tcopies: number;\n\t\t/** Shifts the copies along: copy i is transformed `i + offset` times. */\n\t\toffset: number;\n\t\t/** Each step: move, about `anchor` turn by `rotation` and scale by `scale` percent. */\n\t\tposition: Vec2;\n\t\t/** Offset from the middle of the paths' bounds. */\n\t\tanchor: Vec2;\n\t\trotation: number;\n\t\tscale: Vec2;\n\t\t/** Percent opacity of the first and last copies (in between interpolated). */\n\t\tstartOpacity: number;\n\t\tendOpacity: number;\n\t\t/** `below`: each copy draws under the one before it; `above`: over it. */\n\t\tcomposite: 'below' | 'above';\n\t};\n\t/**\n\t * Envelope distortion: the paths above it are mapped from their combined bounds through a warp\n\t * preset (bend and horizontal / vertical distortion in percent) or, with style `mesh`, through\n\t * `mesh` (null: the flat mesh of `columns × rows` patches over the bounds). The bounds are\n\t * taken afresh each time, so editing the contents keeps the envelope.\n\t */\n\tenvelope: {\n\t\tstyle: VectorWarpStyle | 'mesh';\n\t\tbend: number;\n\t\thorizontal: number;\n\t\tvertical: number;\n\t\torientation: 'horizontal' | 'vertical';\n\t\t/** Patches of the mesh (a preset turns into a mesh of this size when it is edited). */\n\t\trows: number;\n\t\tcolumns: number;\n\t\tmesh: VectorWarpMesh | null;\n\t};\n}\n\n/** One entry of an effect stack. */\nexport type VectorEffect = {\n\t[K in VectorEffectType]: {\n\t\t/** Unique within the node. */\n\t\tid: string;\n\t\ttype: K;\n\t\t/** Disabled effects are skipped. Default true. */\n\t\tenabled?: boolean;\n\t\tparams: VectorEffectParamsMap[K];\n\t};\n}[VectorEffectType];\n\n/** Identifies one segment of a path node: from anchor `segment` to the next. */\nexport interface VectorSegmentRef {\n\tnodeId: string;\n\tsubpath: number;\n\tsegment: number;\n}\n\n/**\n * Visual filters: `dropShadow` (the node's silhouette, offset and blurred, under it),\n * `innerShadow` (a shadow cast inside the edges), `outerGlow` / `innerGlow` (an unoffset glow\n * outside or inside the edges), `layerBlur` (the whole node blurred) and `feather` (edges faded\n * to transparent).\n */\nexport type VectorFilterType = 'dropShadow' | 'innerShadow' | 'outerGlow' | 'innerGlow' | 'layerBlur' | 'feather';\nexport const VECTOR_FILTER_TYPES: readonly VectorFilterType[] = ['dropShadow', 'innerShadow', 'outerGlow', 'innerGlow', 'layerBlur', 'feather'];\n\n/** A shadow or glow: colour, offset, blur and spread in the node's own units. */\ninterface VectorShadowParams {\n\tcolor: string;\n\t/** 0–1: multiplies the colour's own alpha. */\n\topacity: number;\n\t/** Blur radius (the SVG / CSS standard deviation is half of it). */\n\tblur: number;\n\t/** Grows the silhouette before blurring (inner effects: chokes it inwards). */\n\tspread: number;\n}\n\n/** Parameters of each filter type. Lengths are in the node's own units. */\nexport interface VectorFilterParamsMap {\n\tdropShadow: VectorShadowParams & { offsetX: number; offsetY: number };\n\tinnerShadow: VectorShadowParams & { offsetX: number; offsetY: number };\n\touterGlow: VectorShadowParams;\n\tinnerGlow: VectorShadowParams;\n\tlayerBlur: { radius: number };\n\t/** Fades the edges over `radius` inwards. */\n\tfeather: { radius: number };\n}\n\n/** One entry of a node's filter stack. */\nexport type VectorFilter = {\n\t[K in VectorFilterType]: {\n\t\t/** Unique within the node. */\n\t\tid: string;\n\t\ttype: K;\n\t\t/** Disabled filters are skipped. Default true. */\n\t\tenabled?: boolean;\n\t\tparams: VectorFilterParamsMap[K];\n\t};\n}[VectorFilterType];\n","/**\n * Vector gradients: sampling (stop midpoints, smoothness, sRGB / linear-light / Oklab\n * interpolation), geometry (a three-point frame: start, end and the secondary-axis \"width\"\n * point, in the shape's bounding box or its own space; focal point and radius), spread modes and\n * DOM-free SVG paint servers for every style.\n *\n * Linear and radial gradients become `<linearGradient>` / `<radialGradient>` in\n * `userSpaceOnUse` with a `gradientTransform` (so fill, stroke parts and effect copies share one\n * frame, and a box with no height still paints). Midpoints, smoothness and non-sRGB\n * interpolation are not native to SVG: they export as extra stops (`expandVectorGradientStops`)\n * close to the true curve. Reflected gradients are a linear gradient with mirrored stops (or\n * `spreadMethod=\"reflect\"`). Conic and diamond gradients have no SVG element: they become a\n * `<pattern>` in user space whose tiles each carry a linear gradient — 4 exact quadrants for a\n * diamond (t = |u| + |v| is linear in each), thin wedges for a conic gradient.\n *\n * Ported (re-expressed in TypeScript) from the ArtCraft apps (MIT OR Apache-2.0, see\n * THIRD_PARTY_NOTICES.md): vectorcraft `crates/color/src/gradient.rs` (`Gradient::sample_with`\n * midpoint mapping, `expanded_stops`, `reverse`, `span`, stop editing helpers and\n * `GradientGeom::param_at` / `set_focal`), photocraft `crates/compose/src/gradient_fill.rs`\n * (`midpoint_remap`, the Linear / Radial / Angle / Reflected / Diamond styles of `gradient_t`),\n * photocraft `crates/io/src/gradient_bake.rs` (`eval`: smoothness as a Catmull-Rom blend and the\n * interpolation spaces), effectcraft `crates/render/src/shapes.rs` (`GradientRamp::t`, the focal\n * ray), figcraft `crates/color/src/lib.rs` (`GradientKind` angular / diamond and the three-handle\n * unit-box frame) and vectorcraft `crates/svg/src/import/spread.rs` (`expand`: spread modes as\n * repeated stop periods).\n */\nimport { hexToRgba, normalizeTokenColor, rgbaToHex } from '../tokens/color.js';\nimport { rgbToOklab, oklabToRgb } from '../animation/color.js';\nimport { angleToSvgLinearCoords, type SvgGradientDef, type SvgGradientStop } from '../canvas/gradient.js';\nimport type {\n\tVec2,\n\tVectorBounds,\n\tVectorGradient,\n\tVectorGradientInterpolation,\n\tVectorGradientSpread,\n\tVectorGradientStop,\n\tVectorGradientType,\n\tVectorGradientUnits,\n\tVectorMatrix,\n\tVectorPaint\n} from './types.js';\nimport { VECTOR_GRADIENT_INTERPOLATIONS, VECTOR_GRADIENT_SPREADS, VECTOR_GRADIENT_TYPES, VECTOR_STROKE_GRADIENT_MODES } from './types.js';\n\n\n// ---------------------------------------------------------------------------\n// Small maths\n// ---------------------------------------------------------------------------\n\nconst clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));\nconst IDENTITY: VectorMatrix = [1, 0, 0, 1, 0, 0];\n/** `a` then `b`. */\nconst mul = (a: VectorMatrix, b: VectorMatrix): VectorMatrix => [\n\tb[0] * a[0] + b[2] * a[1],\n\tb[1] * a[0] + b[3] * a[1],\n\tb[0] * a[2] + b[2] * a[3],\n\tb[1] * a[2] + b[3] * a[3],\n\tb[0] * a[4] + b[2] * a[5] + b[4],\n\tb[1] * a[4] + b[3] * a[5] + b[5]\n];\nconst apply = (m: VectorMatrix, p: Vec2): Vec2 => ({ x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] });\nconst invert = (m: VectorMatrix): VectorMatrix | null => {\n\tconst [a, b, c, d, e, f] = m;\n\tconst det = a * d - b * c;\n\tif (!Number.isFinite(det) || Math.abs(det) < 1e-18) return null;\n\treturn [d / det, -b / det, -c / det, a / det, (c * f - d * e) / det, (b * e - a * f) / det];\n};\nconst isIdentity = (m: VectorMatrix) => m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;\nconst sub = (a: Vec2, b: Vec2): Vec2 => ({ x: a.x - b.x, y: a.y - b.y });\nconst add = (a: Vec2, b: Vec2): Vec2 => ({ x: a.x + b.x, y: a.y + b.y });\nconst scale = (a: Vec2, k: number): Vec2 => ({ x: a.x * k, y: a.y * k });\nconst dot = (a: Vec2, b: Vec2) => a.x * b.x + a.y * b.y;\n/** 90° clockwise on screen (y down): the secondary axis of a frame. */\nconst perp = (v: Vec2): Vec2 => ({ x: -v.y, y: v.x });\nconst finitePoint = (p: unknown): p is Vec2 => !!p && typeof p === 'object' && Number.isFinite((p as Vec2).x) && Number.isFinite((p as Vec2).y);\n\nconst fmt = (n: number, digits = 4) => {\n\tconst k = 10 ** digits;\n\tconst v = Math.round(n * k) / k;\n\treturn Object.is(v, -0) ? '0' : String(v);\n};\nconst fmtMatrix = (m: VectorMatrix) => `matrix(${m.map((n) => fmt(n, 6)).join(' ')})`;\n\n/** True when `paint` is a gradient (any style). */\nexport function isVectorGradient(paint: VectorPaint | undefined): paint is VectorGradient {\n\treturn !!paint && typeof paint === 'object' && VECTOR_GRADIENT_TYPES.includes((paint as VectorGradient).type) && Array.isArray((paint as VectorGradient).stops);\n}\n\n// ---------------------------------------------------------------------------\n// Colours and interpolation spaces\n// ---------------------------------------------------------------------------\n\n/** Straight-alpha sRGB channels, 0–1. */\nexport type VectorRgba = [number, number, number, number];\n\n/** A CSS colour as 0–1 channels (hex, `rgb()`, `transparent`, `black`, `white`), or null. */\nexport function parseVectorColor(color: string): VectorRgba | null {\n\tconst hex = normalizeTokenColor(color);\n\tif (hex === null) return null;\n\tconst c = hexToRgba(hex)!;\n\treturn [c.r, c.g, c.b, c.a];\n}\n\nconst toLinear = (c: number) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);\nconst toGamma = (c: number) => {\n\tconst v = clamp(c, 0, 1);\n\treturn v <= 0.0031308 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - 0.055;\n};\n\nfunction intoSpace(space: VectorGradientInterpolation, [r, g, b]: readonly number[]): [number, number, number] {\n\tif (space === 'linear') return [toLinear(r), toLinear(g), toLinear(b)];\n\tif (space === 'oklab') {\n\t\tconst c = rgbToOklab(r, g, b);\n\t\treturn [c.l, c.a, c.b];\n\t}\n\treturn [r, g, b];\n}\n\nfunction outOfSpace(space: VectorGradientInterpolation, [x, y, z]: readonly number[]): [number, number, number] {\n\tif (space === 'linear') return [toGamma(x), toGamma(y), toGamma(z)];\n\tif (space === 'oklab') {\n\t\tconst c = oklabToRgb({ l: x, a: y, b: z, alpha: 1 });\n\t\treturn [c.r, c.g, c.b];\n\t}\n\treturn [clamp(x, 0, 1), clamp(y, 0, 1), clamp(z, 0, 1)];\n}\n\n// ---------------------------------------------------------------------------\n// Stops\n// ---------------------------------------------------------------------------\n\n/** Least and most a midpoint can be (the stop editors clamp further, to 0.05–0.95). */\nconst MID_MIN = 0.01;\nconst MID_MAX = 0.99;\n/** Fewest stops a gradient keeps when one is removed. */\nexport const VECTOR_GRADIENT_MIN_STOPS = 2;\n\n/**\n * Where relative position `u` of a segment lands once its midpoint is `m` (0.5 = unchanged): the\n * colour is half-way at `m`, linear on either side (photocraft `midpoint_remap`).\n */\nexport function gradientMidpointRemap(u: number, m: number): number {\n\tconst mid = clamp(m, MID_MIN, MID_MAX);\n\tif (Math.abs(mid - 0.5) < 1e-6) return u;\n\treturn u <= mid ? (0.5 * u) / mid : 0.5 + (0.5 * (u - mid)) / (1 - mid);\n}\n\nconst hasMidpoint = (s: VectorGradientStop) => s.midpoint !== undefined && Number.isFinite(s.midpoint) && Math.abs(s.midpoint - 0.5) > 1e-3;\n\n/** The stops sorted by offset (stable), offsets clamped to 0–1, unreadable offsets dropped. */\nexport function sortedVectorGradientStops(g: Pick<VectorGradient, 'stops'>): VectorGradientStop[] {\n\treturn (g.stops ?? [])\n\t\t.filter((s) => s && Number.isFinite(s.offset) && typeof s.color === 'string')\n\t\t.map((s, i) => ({ s: { ...s, offset: clamp(s.offset, 0, 1) }, i }))\n\t\t.sort((a, b) => a.s.offset - b.s.offset || a.i - b.i)\n\t\t.map((e) => e.s);\n}\n\n/** Does the gradient need extra stops to look the same in SVG (midpoints, smoothness or a non-sRGB space)? */\nexport function vectorGradientNeedsExpansion(g: VectorGradient): boolean {\n\treturn (g.interpolation ?? 'srgb') !== 'srgb' || (g.smoothness ?? 0) > 0 || (g.stops ?? []).some(hasMidpoint);\n}\n\ninterface RampPoint {\n\tt: number;\n\t/** Colour channels in the interpolation space. */\n\tv: [number, number, number];\n\t/** Colour alpha × opacity. */\n\ta: number;\n\tmid: number;\n}\n\n/** A gradient's colour ramp, prepared for sampling. */\nexport interface VectorGradientRamp {\n\t/** Straight-alpha sRGB at `t` (clamped to 0–1). */\n\tsample(t: number): VectorRgba;\n}\n\n/**\n * The colour ramp of a gradient (midpoints, smoothness and interpolation space applied), or null\n * when a stop colour is not one `parseVectorColor` reads.\n */\nexport function vectorGradientRamp(g: Pick<VectorGradient, 'stops' | 'interpolation' | 'smoothness'>): VectorGradientRamp | null {\n\tconst space = g.interpolation && VECTOR_GRADIENT_INTERPOLATIONS.includes(g.interpolation) ? g.interpolation : 'srgb';\n\tconst smooth = clamp(Number.isFinite(g.smoothness) ? (g.smoothness as number) : 0, 0, 1);\n\tconst pts: RampPoint[] = [];\n\tfor (const s of sortedVectorGradientStops(g)) {\n\t\tconst c = parseVectorColor(s.color);\n\t\tif (!c) return null;\n\t\tpts.push({ t: s.offset, v: intoSpace(space, c), a: c[3] * clamp(s.opacity ?? 1, 0, 1), mid: s.midpoint ?? 0.5 });\n\t}\n\treturn {\n\t\tsample(t: number): VectorRgba {\n\t\t\tconst n = pts.length;\n\t\t\tif (!n) return [0, 0, 0, 0];\n\t\t\tconst first = pts[0];\n\t\t\tconst last = pts[n - 1];\n\t\t\tconst at = (p: RampPoint): VectorRgba => [...outOfSpace(space, p.v), p.a] as VectorRgba;\n\t\t\tif (!(t > first.t) || n < 2) return at(first);\n\t\t\tif (t >= last.t) return at(last);\n\t\t\tlet i = 0;\n\t\t\twhile (i + 2 < n && !(t >= pts[i].t && t <= pts[i + 1].t)) i++;\n\t\t\tconst p0 = pts[i];\n\t\t\tconst p1 = pts[i + 1];\n\t\t\tconst h = Math.max(p1.t - p0.t, 1e-6);\n\t\t\tconst u = gradientMidpointRemap((t - p0.t) / h, p0.mid);\n\t\t\tconst alpha = p0.a + (p1.a - p0.a) * u;\n\t\t\tif (smooth <= 0) return [...outOfSpace(space, [0, 1, 2].map((c) => p0.v[c] + (p1.v[c] - p0.v[c]) * u)), alpha] as VectorRgba;\n\t\t\t// Catmull-Rom tangents per unit t; the ends use half the chord slope (gradient_bake `eval`).\n\t\t\tconst tangent = (k: number) => {\n\t\t\t\tconst [a, b] = k === 0 ? [0, 1] : k === n - 1 ? [n - 2, n - 1] : [k - 1, k + 1];\n\t\t\t\tconst d = Math.max(pts[b].t - pts[a].t, 1e-6);\n\t\t\t\tconst half = k === 0 || k === n - 1 ? 0.5 : 1;\n\t\t\t\treturn [0, 1, 2].map((c) => ((pts[b].v[c] - pts[a].v[c]) / d) * half);\n\t\t\t};\n\t\t\tconst m0 = tangent(i);\n\t\t\tconst m1 = tangent(i + 1);\n\t\t\tconst u2 = u * u;\n\t\t\tconst u3 = u2 * u;\n\t\t\tconst h00 = 2 * u3 - 3 * u2 + 1;\n\t\t\tconst h10 = u3 - 2 * u2 + u;\n\t\t\tconst h01 = -2 * u3 + 3 * u2;\n\t\t\tconst h11 = u3 - u2;\n\t\t\tconst v = [0, 1, 2].map((c) => {\n\t\t\t\tconst linear = p0.v[c] + (p1.v[c] - p0.v[c]) * u;\n\t\t\t\tconst spline = h00 * p0.v[c] + h10 * h * m0[c] + h01 * p1.v[c] + h11 * h * m1[c];\n\t\t\t\tconst value = linear + (spline - linear) * smooth;\n\t\t\t\t// Smoothness bends the ramp without inventing colours outside the segment.\n\t\t\t\treturn clamp(value, Math.min(p0.v[c], p1.v[c]), Math.max(p0.v[c], p1.v[c]));\n\t\t\t});\n\t\t\treturn [...outOfSpace(space, v), alpha] as VectorRgba;\n\t\t}\n\t};\n}\n\n/** What the gradient paints at `t` (0–1 along its stops): `#rrggbb` and opacity. */\nexport function sampleVectorGradient(g: VectorGradient, t: number): { color: string; opacity: number } {\n\tconst ramp = vectorGradientRamp(g);\n\tif (ramp) {\n\t\tconst [r, gg, b, a] = ramp.sample(t);\n\t\treturn { color: rgbaToHex(r, gg, b), opacity: a };\n\t}\n\t// Unreadable colours: the nearest stop at or before `t`.\n\tconst stops = sortedVectorGradientStops(g);\n\tconst s = [...stops].reverse().find((x) => x.offset <= t) ?? stops[0];\n\treturn s ? { color: s.color, opacity: s.opacity ?? 1 } : { color: '#000000', opacity: 0 };\n}\n\nconst stopOf = (offset: number, c: VectorRgba): VectorGradientStop => {\n\tconst color = rgbaToHex(c[0], c[1], c[2]);\n\tconst opacity = Math.round(c[3] * 1e4) / 1e4;\n\treturn opacity < 1 ? { offset, color, opacity } : { offset, color };\n};\n\nconst maxDiff = (a: VectorRgba, b: VectorRgba) => Math.max(Math.abs(a[0] - b[0]), Math.abs(a[1] - b[1]), Math.abs(a[2] - b[2]), Math.abs(a[3] - b[3]));\nconst lerpRgba = (a: VectorRgba, b: VectorRgba, u: number): VectorRgba => [0, 1, 2, 3].map((c) => a[c] + (b[c] - a[c]) * u) as VectorRgba;\n\n/**\n * Plain stops (no midpoints) that SVG's sRGB interpolation turns into the gradient's colours:\n * the stops as they are when nothing needs expanding; one extra stop per off-centre midpoint for\n * sRGB without smoothness (exact: the midpoint makes each half linear); otherwise stops placed\n * adaptively until linear interpolation between them is within `tolerance` (0–1 per channel,\n * default half a level) of the true curve everywhere they were checked.\n */\nexport function expandVectorGradientStops(g: VectorGradient, options: { tolerance?: number } = {}): VectorGradientStop[] {\n\tconst stops = sortedVectorGradientStops(g);\n\tconst plain = () => stops.map(({ offset, color, opacity }) => (opacity !== undefined && opacity !== 1 ? { offset, color, opacity } : { offset, color }));\n\tif (!vectorGradientNeedsExpansion(g)) return plain();\n\tconst ramp = vectorGradientRamp(g);\n\tif (!ramp || stops.length < 2) return plain();\n\tconst tol = options.tolerance ?? 0.5 / 255;\n\tconst sRgbOnly = (g.interpolation ?? 'srgb') === 'srgb' && !((g.smoothness ?? 0) > 0);\n\tconst own = (s: VectorGradientStop): VectorRgba => {\n\t\tconst c = parseVectorColor(s.color)!;\n\t\treturn [c[0], c[1], c[2], c[3] * clamp(s.opacity ?? 1, 0, 1)];\n\t};\n\tconst out: VectorGradientStop[] = [];\n\tconst push = (offset: number, c: VectorRgba) => out.push(stopOf(offset, c));\n\tfor (let i = 0; i < stops.length; i++) {\n\t\tconst a = stops[i];\n\t\tconst b = stops[i + 1];\n\t\tpush(a.offset, own(a));\n\t\tif (!b || b.offset - a.offset < 1e-9) continue;\n\t\tconst knots = [a.offset];\n\t\tif (hasMidpoint(a)) knots.push(a.offset + (b.offset - a.offset) * clamp(a.midpoint!, MID_MIN, MID_MAX));\n\t\tknots.push(b.offset);\n\t\t// Samples just inside the segment (the ramp at its ends is the neighbouring segment's).\n\t\tconst inside = (t: number) => ramp.sample(clamp(t, a.offset + 1e-9, b.offset - 1e-9));\n\t\tfor (let k = 0; k + 1 < knots.length; k++) {\n\t\t\tconst t0 = knots[k];\n\t\t\tconst t1 = knots[k + 1];\n\t\t\tif (k > 0) push(t0, inside(t0));\n\t\t\tif (sRgbOnly) continue;\n\t\t\tconst refine = (x0: number, c0: VectorRgba, x1: number, c1: VectorRgba, depth: number) => {\n\t\t\t\tconst xm = (x0 + x1) / 2;\n\t\t\t\tconst cm = inside(xm);\n\t\t\t\t// Checked near the ends too: gamma curves bend hardest next to a black channel.\n\t\t\t\tconst ok = depth >= 8 || [0.03, 0.1, 0.25, 0.5, 0.75, 0.9, 0.97].every((f) => maxDiff(inside(x0 + (x1 - x0) * f), lerpRgba(c0, c1, f)) <= tol);\n\t\t\t\tif (ok) return;\n\t\t\t\trefine(x0, c0, xm, cm, depth + 1);\n\t\t\t\tpush(xm, cm);\n\t\t\t\trefine(xm, cm, x1, c1, depth + 1);\n\t\t\t};\n\t\t\trefine(t0, inside(t0), t1, inside(t1), 0);\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Linear interpolation between plain stops (what SVG draws from them), straight alpha. */\nexport function samplePlainStops(stops: readonly VectorGradientStop[], t: number): VectorRgba {\n\tconst parsed = stops.map((s) => {\n\t\tconst c = parseVectorColor(s.color) ?? [0, 0, 0, 1];\n\t\treturn { t: s.offset, c: [c[0], c[1], c[2], c[3] * (s.opacity ?? 1)] as VectorRgba };\n\t});\n\tif (!parsed.length) return [0, 0, 0, 0];\n\tif (t <= parsed[0].t) return parsed[0].c;\n\tfor (let i = 0; i + 1 < parsed.length; i++) {\n\t\tconst a = parsed[i];\n\t\tconst b = parsed[i + 1];\n\t\tif (t <= b.t) return b.t - a.t < 1e-12 ? b.c : lerpRgba(a.c, b.c, (t - a.t) / (b.t - a.t));\n\t}\n\treturn parsed[parsed.length - 1].c;\n}\n\n/** The gradient with its stops reversed, each midpoint measured from the other side (vectorcraft `reverse`). */\nexport function reverseVectorGradient<T extends Pick<VectorGradient, 'stops'>>(g: T): T {\n\tconst stops = sortedVectorGradientStops(g).reverse();\n\tconst next = stops.map((s, i) => {\n\t\tconst following = stops[i + 1];\n\t\tconst mid = following ? 1 - (following.midpoint ?? 0.5) : 0.5;\n\t\tconst { midpoint: _m, ...rest } = s;\n\t\treturn { ...rest, offset: 1 - s.offset, ...(Math.abs(mid - 0.5) > 1e-6 ? { midpoint: mid } : {}) };\n\t});\n\treturn { ...g, stops: next };\n}\n\n/** Stops with `stop` placed by offset (after any at the same offset); returns its index. */\nfunction placeStop(stops: VectorGradientStop[], stop: VectorGradientStop): number {\n\tlet i = stops.findIndex((o) => o.offset > stop.offset);\n\tif (i < 0) i = stops.length;\n\tstops.splice(i, 0, stop);\n\treturn i;\n}\n\n/** A stop added at `offset` painting what the gradient paints there; returns the stops and its index. */\nexport function insertVectorGradientStop(g: VectorGradient, offset: number): { stops: VectorGradientStop[]; index: number } {\n\tconst at = clamp(offset, 0, 1);\n\tconst { color, opacity } = sampleVectorGradient(g, at);\n\tconst stops = sortedVectorGradientStops(g);\n\tconst index = placeStop(stops, opacity < 1 ? { offset: at, color, opacity } : { offset: at, color });\n\treturn { stops, index };\n}\n\n/** Stop `index` moved to `offset` (re-sorted); returns the stops and its new index. */\nexport function moveVectorGradientStop(stops: readonly VectorGradientStop[], index: number, offset: number): { stops: VectorGradientStop[]; index: number } {\n\tconst next = stops.map((s) => ({ ...s }));\n\tif (index < 0 || index >= next.length) return { stops: next, index };\n\tconst [s] = next.splice(index, 1);\n\treturn { stops: next, index: placeStop(next, { ...s, offset: clamp(offset, 0, 1) }) };\n}\n\n/** The stops without stop `index`, or null when that would leave fewer than two. */\nexport function removeVectorGradientStop(stops: readonly VectorGradientStop[], index: number): VectorGradientStop[] | null {\n\tif (stops.length <= VECTOR_GRADIENT_MIN_STOPS || index < 0 || index >= stops.length) return null;\n\treturn stops.filter((_, i) => i !== index);\n}\n\n/** Where (0–1 along the gradient) the midpoint after stop `index` sits, or null for the last stop. */\nexport function vectorGradientMidpointPosition(stops: readonly VectorGradientStop[], index: number): number | null {\n\tconst a = stops[index];\n\tconst b = stops[index + 1];\n\tif (!a || !b) return null;\n\treturn a.offset + (b.offset - a.offset) * (a.midpoint ?? 0.5);\n}\n\n/** The midpoint of stop `index` that puts it at position `pos` (clamped to 0.05–0.95), or null. */\nexport function vectorGradientMidpointFromPosition(stops: readonly VectorGradientStop[], index: number, pos: number): number | null {\n\tconst a = stops[index];\n\tconst b = stops[index + 1];\n\tif (!a || !b) return null;\n\tconst span = Math.max(b.offset - a.offset, 1e-6);\n\treturn clamp((pos - a.offset) / span, 0.05, 0.95);\n}\n\n/**\n * The part of the gradient from `from` to `to` (0–1, either order) as plain stops over 0–1\n * (vectorcraft `span`): the colours at both ends plus the expanded stops between them.\n */\nexport function spanVectorGradientStops(g: VectorGradient, from: number, to: number, expanded = expandVectorGradientStops(g)): VectorGradientStop[] {\n\tif (to < from) {\n\t\tconst s = spanVectorGradientStops(g, to, from, expanded);\n\t\treturn s.map((x) => ({ ...x, offset: 1 - x.offset })).reverse();\n\t}\n\tconst d = to - from;\n\tconst at = (offset: number, t: number) => stopOf(offset, samplePlainStops(expanded, t));\n\tconst out = [at(0, from)];\n\tif (d > 1e-9) for (const s of expanded) if (s.offset > from && s.offset < to) out.push({ ...s, offset: (s.offset - from) / d });\n\tout.push(at(1, to));\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Geometry\n// ---------------------------------------------------------------------------\n\n/** A gradient's frame points, in its units (see `VectorGradient`). */\nexport interface VectorGradientPoints {\n\tstart: Vec2;\n\tend: Vec2;\n\twidth: Vec2;\n\tfocal: Vec2 | null;\n\tfocalRadius: number;\n\tunits: VectorGradientUnits;\n}\n\n/** Whether the gradient carries its own points (rather than the angle / centre shorthand). */\nexport const hasExplicitGradientGeometry = (g: VectorGradient) => finitePoint(g.start) && finitePoint(g.end);\n\n/** The shorthand of a gradient as points in its bounding box. */\nfunction shorthandPoints(g: VectorGradient): { start: Vec2; end: Vec2; width: Vec2 } {\n\tif (g.type === 'linear' || g.type === 'reflected') {\n\t\tconst c = angleToSvgLinearCoords(Number.isFinite(g.angle) ? (g.angle as number) : 180);\n\t\tconst start = g.type === 'linear' ? { x: c.x1, y: c.y1 } : { x: 0.5, y: 0.5 };\n\t\tconst end = { x: c.x2, y: c.y2 };\n\t\treturn { start, end, width: add(start, perp(sub(end, start))) };\n\t}\n\tconst start = { x: Number.isFinite(g.cx) ? (g.cx as number) : 0.5, y: Number.isFinite(g.cy) ? (g.cy as number) : 0.5 };\n\tconst r = Number.isFinite(g.r) ? (g.r as number) : 0.5;\n\tif (g.type === 'radial') return { start, end: { x: start.x + r, y: start.y }, width: { x: start.x, y: start.y + r } };\n\tconst a = ((Number.isFinite(g.angle) ? (g.angle as number) : 90) * Math.PI) / 180;\n\tconst dir = { x: Math.sin(a), y: -Math.cos(a) };\n\treturn { start, end: add(start, scale(dir, r)), width: add(start, scale(perp(dir), r)) };\n}\n\n/** The gradient's frame points in its units, the shorthand resolved (it is always in the box). */\nexport function vectorGradientPoints(g: VectorGradient): VectorGradientPoints {\n\tconst explicit = hasExplicitGradientGeometry(g);\n\tconst base = explicit ? null : shorthandPoints(g);\n\tconst start = explicit ? { x: g.start!.x, y: g.start!.y } : base!.start;\n\tconst end = explicit ? { x: g.end!.x, y: g.end!.y } : base!.end;\n\tconst width = explicit ? (finitePoint(g.width) ? { x: g.width.x, y: g.width.y } : add(start, perp(sub(end, start)))) : base!.width;\n\treturn {\n\t\tstart,\n\t\tend,\n\t\twidth,\n\t\tfocal: g.type === 'radial' && finitePoint(g.focal) ? { x: g.focal.x, y: g.focal.y } : null,\n\t\tfocalRadius: g.type === 'radial' ? clamp(Number.isFinite(g.focalRadius) ? (g.focalRadius as number) : 0, 0, 0.99) : 0,\n\t\tunits: explicit && g.units === 'userSpaceOnUse' ? 'userSpaceOnUse' : 'objectBoundingBox'\n\t};\n}\n\n/**\n * The unit box → `bounds` matrix of `objectBoundingBox` gradients. A side with no extent (a\n * horizontal or vertical line) takes the other side's, centred, so the gradient still paints\n * (SVG paints nothing there).\n */\nexport function vectorBoxMatrix(bounds: VectorBounds | null | undefined): VectorMatrix {\n\tif (!bounds) return IDENTITY;\n\tlet { x, y, width: w, height: h } = bounds;\n\tconst fallback = Math.max(w, h, 1e-9) > 1e-9 ? Math.max(w, h) : 1;\n\tif (!(w > 1e-9)) {\n\t\tx -= fallback / 2 - w / 2;\n\t\tw = fallback;\n\t}\n\tif (!(h > 1e-9)) {\n\t\ty -= fallback / 2 - h / 2;\n\t\th = fallback;\n\t}\n\treturn [w, 0, 0, h, x, y];\n}\n\n/** The matrix of frame points: unit frame (u along start → end, v along start → width) → their space. */\nconst framePointsMatrix = (p: Pick<VectorGradientPoints, 'start' | 'end' | 'width'>): VectorMatrix => [p.end.x - p.start.x, p.end.y - p.start.y, p.width.x - p.start.x, p.width.y - p.start.y, p.start.x, p.start.y];\n\n/** The gradient's units → node space (the box matrix for `objectBoundingBox`, else the identity). */\nexport function vectorGradientUnitsMatrix(g: VectorGradient, bounds: VectorBounds | null | undefined): VectorMatrix {\n\treturn vectorGradientPoints(g).units === 'objectBoundingBox' ? vectorBoxMatrix(bounds) : IDENTITY;\n}\n\n/** The gradient's unit frame → node space: u = 1 at the end, v = 1 at the width point. */\nexport function vectorGradientMatrix(g: VectorGradient, bounds: VectorBounds | null | undefined): VectorMatrix {\n\treturn mul(framePointsMatrix(vectorGradientPoints(g)), vectorGradientUnitsMatrix(g, bounds));\n}\n\n/** Most a focal point reaches towards the edge of the circle (vectorcraft `FOCAL_MAX`). */\nconst FOCAL_MAX = 0.99;\n\n/** The focal point in the unit frame, pulled inside the circle (or null). */\nfunction unitFocal(g: VectorGradient): { f: Vec2; fr: number } | null {\n\tconst p = vectorGradientPoints(g);\n\tif (!p.focal && !(p.focalRadius > 0)) return null;\n\tconst inv = invert(framePointsMatrix(p));\n\tif (!inv) return null;\n\tlet f = p.focal ? apply(inv, p.focal) : { x: 0, y: 0 };\n\tconst reach = FOCAL_MAX * (1 - p.focalRadius);\n\tconst r = Math.hypot(f.x, f.y);\n\tif (r > reach) f = scale(f, reach / r);\n\treturn { f, fr: p.focalRadius };\n}\n\n/**\n * t of a radial gradient at unit-frame point `q` with the focal circle centred on `f` with radius\n * `fr` and the end circle the unit circle (SVG's two-point conical gradient). With `fr = 0` this\n * is the focal ray: t is the distance from the focal point to `q` over the distance from it to\n * the circle along the same ray (effectcraft `GradientRamp::t`, vectorcraft `param_at`).\n */\nexport function vectorGradientFocalParam(q: Vec2, f: Vec2 = { x: 0, y: 0 }, fr = 0): number {\n\tconst d = sub(q, f);\n\tconst dr = 1 - fr;\n\tconst a = dot(f, f) - dr * dr;\n\tconst b = dot(d, f) - fr * dr;\n\tconst c = dot(d, d) - fr * fr;\n\tif (Math.abs(a) < 1e-12) return Math.abs(b) < 1e-12 ? 0 : -c / (2 * b);\n\treturn (-b - Math.sqrt(Math.max(0, b * b - a * c))) / a;\n}\n\n/**\n * The raw gradient parameter (before the spread) of a style at unit-frame point `q`: linear `u`,\n * reflected `|u|`, diamond `|u| + |v|`, conic the turn from the u axis towards the v axis (0–1),\n * radial the distance through the focal circle (photocraft `gradient_t`).\n */\nexport function vectorGradientParam(type: VectorGradientType, q: Vec2, focal?: { f: Vec2; fr: number } | null): number {\n\tswitch (type) {\n\t\tcase 'linear':\n\t\t\treturn q.x;\n\t\tcase 'reflected':\n\t\t\treturn Math.abs(q.x);\n\t\tcase 'diamond':\n\t\t\treturn Math.abs(q.x) + Math.abs(q.y);\n\t\tcase 'conic': {\n\t\t\tconst turn = Math.atan2(q.y, q.x) / (2 * Math.PI);\n\t\t\treturn turn - Math.floor(turn);\n\t\t}\n\t\tcase 'radial':\n\t\t\treturn focal ? vectorGradientFocalParam(q, focal.f, focal.fr) : Math.hypot(q.x, q.y);\n\t}\n}\n\n/** `t` folded into 0–1 by a spread mode. */\nexport function applyVectorGradientSpread(t: number, spread: VectorGradientSpread = 'pad'): number {\n\tif (!Number.isFinite(t)) return 0;\n\tif (spread === 'repeat') return t - Math.floor(t);\n\tif (spread === 'reflect') {\n\t\tconst m = ((t % 2) + 2) % 2;\n\t\treturn m <= 1 ? m : 2 - m;\n\t}\n\treturn clamp(t, 0, 1);\n}\n\n/** The gradient's t (spread applied) at node-space point `p` of a shape with box `bounds`. */\nexport function vectorGradientTAt(g: VectorGradient, bounds: VectorBounds | null | undefined, p: Vec2): number {\n\tconst inv = invert(vectorGradientMatrix(g, bounds));\n\tif (!inv) return 0;\n\tconst raw = vectorGradientParam(g.type, apply(inv, p), g.type === 'radial' ? unitFocal(g) : null);\n\treturn g.type === 'conic' ? raw : applyVectorGradientSpread(raw, g.spread ?? 'pad');\n}\n\n/** What the gradient paints at node-space point `p` (straight-alpha sRGB, 0–1). */\nexport function sampleVectorGradientAt(g: VectorGradient, bounds: VectorBounds | null | undefined, p: Vec2): VectorRgba {\n\tconst t = vectorGradientTAt(g, bounds, p);\n\tconst ramp = vectorGradientRamp(g);\n\treturn ramp ? ramp.sample(t) : samplePlainStops(sortedVectorGradientStops(g), t);\n}\n\n/** The main axis length over the secondary's: |width − start| / |end − start| (signed by side). */\nexport function vectorGradientAspect(g: VectorGradient): number {\n\tconst p = vectorGradientPoints(g);\n\tconst u = sub(p.end, p.start);\n\tconst l2 = dot(u, u);\n\treturn l2 > 1e-18 ? dot(sub(p.width, p.start), perp(u)) / l2 : 1;\n}\n\n/** The gradient with explicit points (in its units) replacing the shorthand. */\nexport function explicitVectorGradient(g: VectorGradient): VectorGradient {\n\tconst p = vectorGradientPoints(g);\n\tconst { angle: _a, cx: _cx, cy: _cy, r: _r, ...rest } = g;\n\treturn {\n\t\t...rest,\n\t\tstart: p.start,\n\t\tend: p.end,\n\t\twidth: p.width,\n\t\t...(p.focal ? { focal: p.focal } : {}),\n\t\tunits: p.units\n\t};\n}\n\n/**\n * The gradient in the node's own space (`userSpaceOnUse`), painting what it paints on a shape\n * with box `bounds` (vectorcraft `GradientPaint::pin`): its points mapped through the box.\n */\nexport function pinVectorGradient(g: VectorGradient, bounds: VectorBounds | null | undefined): VectorGradient {\n\tconst e = explicitVectorGradient(g);\n\tif (e.units === 'userSpaceOnUse') return e;\n\tconst m = vectorBoxMatrix(bounds);\n\treturn { ...e, start: apply(m, e.start!), end: apply(m, e.end!), width: apply(m, e.width!), ...(e.focal ? { focal: apply(m, e.focal) } : {}), units: 'userSpaceOnUse' };\n}\n\n/**\n * The gradient after the node's geometry is mapped by `m`: a `userSpaceOnUse` gradient's points\n * are mapped too (any affine map keeps every style's t, since t is measured in the frame the\n * three points span); a box gradient follows the box and is unchanged.\n */\nexport function transformVectorGradient(g: VectorGradient, m: VectorMatrix): VectorGradient {\n\tif (!hasExplicitGradientGeometry(g) || g.units !== 'userSpaceOnUse' || isIdentity(m)) return g;\n\tconst p = vectorGradientPoints(g);\n\treturn { ...g, start: apply(m, p.start), end: apply(m, p.end), width: apply(m, p.width), ...(p.focal ? { focal: apply(m, p.focal) } : {}) };\n}\n\n/**\n * A gradient after its node's geometry goes through a non-affine map `f` (Free Distort, an\n * envelope): handles in the node's own space (`userSpaceOnUse`) are mapped point by point, which\n * keeps the gradient's frame where the shape went; bounding-box gradients are left alone, as they\n * already follow the shape's new bounds.\n */\nexport function mapVectorGradient(g: VectorGradient, f: (p: Vec2) => Vec2): VectorGradient {\n\tif (!hasExplicitGradientGeometry(g) || g.units !== 'userSpaceOnUse') return g;\n\tconst p = vectorGradientPoints(g);\n\treturn { ...g, start: f(p.start), end: f(p.end), width: f(p.width), ...(p.focal ? { focal: f(p.focal) } : {}) };\n}\n\n/** A paint after its node's geometry goes through a non-affine map `f` (see `mapVectorGradient`). */\nexport function mapVectorPaint<T extends VectorPaint | undefined>(paint: T, f: (p: Vec2) => Vec2): T {\n\treturn (isVectorGradient(paint) ? mapVectorGradient(paint, f) : paint) as T;\n}\n\n/** A paint after its node's geometry is mapped by `m` (colours unchanged). */\nexport function transformVectorPaint<T extends VectorPaint | undefined>(paint: T, m: VectorMatrix): T {\n\treturn (isVectorGradient(paint) ? transformVectorGradient(paint, m) : paint) as T;\n}\n\n// ---------------------------------------------------------------------------\n// Handles (on-canvas editing)\n// ---------------------------------------------------------------------------\n\n/**\n * An on-canvas handle of a gradient: `start` (a radial, conic or diamond gradient's centre),\n * `end` (a linear or reflected gradient's end), `radius` (the end of the others), `aspect` (the\n * secondary axis' end) and `focal` (radial).\n */\nexport type VectorGradientHandle = 'start' | 'end' | 'radius' | 'aspect' | 'focal';\n\n/** Whether the style has a centre with a radius (rather than a start and an end). */\nexport const isCentredVectorGradient = (type: VectorGradientType) => type === 'radial' || type === 'conic' || type === 'diamond';\n\n/** The handles of a gradient in the space `toWorld` maps the node's space to (the document, the screen). */\nexport function vectorGradientHandles(\n\tg: VectorGradient,\n\tbounds: VectorBounds | null | undefined,\n\ttoWorld: VectorMatrix = IDENTITY\n): { start: Vec2; end: Vec2; aspect: Vec2 | null; focal: Vec2 | null } {\n\tconst p = vectorGradientPoints(g);\n\tconst m = mul(vectorGradientUnitsMatrix(g, bounds), toWorld);\n\tconst f = g.type === 'radial' ? unitFocal(g) : null;\n\tconst frame = mul(framePointsMatrix(p), m);\n\treturn {\n\t\tstart: apply(m, p.start),\n\t\tend: apply(m, p.end),\n\t\taspect: isCentredVectorGradient(g.type) ? apply(m, p.width) : null,\n\t\tfocal: g.type === 'radial' ? apply(frame, f?.f ?? { x: 0, y: 0 }) : null\n\t};\n}\n\n/** Keeps the width point (and focal point) where they were relative to the main axis when it moves. */\nfunction withMainAxis(p: VectorGradientPoints, start: Vec2, end: Vec2): Pick<VectorGradientPoints, 'start' | 'end' | 'width' | 'focal'> {\n\tconst before = invert(framePointsMatrix(p));\n\tconst u = sub(p.end, p.start);\n\tconst l2 = dot(u, u);\n\tconst w = sub(p.width, p.start);\n\tconst rel = l2 > 1e-18 ? { a: dot(w, u) / l2, b: dot(w, perp(u)) / l2 } : { a: 0, b: 1 };\n\tconst u2 = sub(end, start);\n\tconst width = add(start, add(scale(u2, rel.a), scale(perp(u2), rel.b)));\n\tconst next = { start, end, width };\n\tconst focal = p.focal && before ? apply(framePointsMatrix(next), apply(before, p.focal)) : p.focal;\n\treturn { ...next, focal };\n}\n\n/**\n * The gradient (pinned to the node's space) with a handle dragged to node-space point `to`.\n * `start` moves the whole gradient by `to − from`; `end` / `radius` move the end, the secondary\n * axis and focal point following; `aspect` sets the secondary axis' length square to the main\n * one; `focal` sets the focal point (pulled inside the circle; null centres it).\n */\nexport function moveVectorGradientHandle(\n\tg: VectorGradient,\n\tbounds: VectorBounds | null | undefined,\n\thandle: VectorGradientHandle,\n\tto: Vec2,\n\tfrom: Vec2 = to\n): VectorGradient {\n\tconst pinned = pinVectorGradient(g, bounds);\n\tconst p = vectorGradientPoints(pinned);\n\tconst set = (q: Partial<Pick<VectorGradient, 'start' | 'end' | 'width' | 'focal'>>) => {\n\t\tconst next = { ...pinned, ...q };\n\t\tif (next.focal === null || next.focal === undefined) delete next.focal;\n\t\treturn next;\n\t};\n\tswitch (handle) {\n\t\tcase 'start': {\n\t\t\tconst d = sub(to, from);\n\t\t\treturn set({ start: add(p.start, d), end: add(p.end, d), width: add(p.width, d), focal: p.focal ? add(p.focal, d) : null });\n\t\t}\n\t\tcase 'end':\n\t\tcase 'radius': {\n\t\t\tif (Math.hypot(to.x - p.start.x, to.y - p.start.y) < 1e-6) return pinned;\n\t\t\tconst n = withMainAxis(p, p.start, to);\n\t\t\treturn set({ end: n.end, width: n.width, focal: n.focal });\n\t\t}\n\t\tcase 'aspect': {\n\t\t\tconst u = sub(p.end, p.start);\n\t\t\tconst l2 = dot(u, u);\n\t\t\tif (l2 < 1e-18) return pinned;\n\t\t\tlet aspect = dot(sub(to, p.start), perp(u)) / l2;\n\t\t\tif (Math.abs(aspect) < 0.01) aspect = aspect < 0 ? -0.01 : 0.01;\n\t\t\tconst before = invert(framePointsMatrix(p));\n\t\t\tconst width = add(p.start, scale(perp(u), aspect));\n\t\t\tconst focal = p.focal && before ? apply(framePointsMatrix({ start: p.start, end: p.end, width }), apply(before, p.focal)) : null;\n\t\t\treturn set({ width, focal });\n\t\t}\n\t\tcase 'focal': {\n\t\t\tconst frame = framePointsMatrix(p);\n\t\t\tconst inv = invert(frame);\n\t\t\tif (!inv) return pinned;\n\t\t\tlet q = apply(inv, to);\n\t\t\tconst r = Math.hypot(q.x, q.y);\n\t\t\tif (r < 1e-6) return set({ focal: null });\n\t\t\tconst reach = FOCAL_MAX * (1 - p.focalRadius);\n\t\t\tif (r > reach) q = scale(q, reach / r);\n\t\t\treturn set({ focal: apply(frame, q) });\n\t\t}\n\t}\n}\n\n/** The gradient set to run from `start` to `end` (node space), its secondary axis square to it with its aspect kept. */\nexport function setVectorGradientVector(g: VectorGradient, bounds: VectorBounds | null | undefined, start: Vec2, end: Vec2): VectorGradient {\n\tconst pinned = pinVectorGradient(g, bounds);\n\tconst p = vectorGradientPoints(pinned);\n\tconst aspect = Math.abs(vectorGradientAspect(pinned)) > 1e-6 ? vectorGradientAspect(pinned) : 1;\n\tconst u = sub(end, start);\n\tconst width = add(start, scale(perp(u), aspect));\n\tconst before = invert(framePointsMatrix(p));\n\tconst focal = p.focal && before ? apply(framePointsMatrix({ start, end, width }), apply(before, p.focal)) : null;\n\tconst next: VectorGradient = { ...pinned, start, end, width };\n\tif (focal) next.focal = focal;\n\telse delete next.focal;\n\treturn next;\n}\n\n// ---------------------------------------------------------------------------\n// Spread as stop periods\n// ---------------------------------------------------------------------------\n\n/** Most periods a spread is expanded to (vectorcraft `MAX_PERIODS`). */\nexport const VECTOR_GRADIENT_MAX_PERIODS = 64;\n\n/** The stops of one period, from 0 to 1: the end colours held out to the ends (vectorcraft `whole_period`). */\nfunction wholePeriod(stops: readonly VectorGradientStop[]): VectorGradientStop[] {\n\tconst out = stops.map((s) => ({ ...s }));\n\tif (out.length && out[0].offset > 0) out.unshift({ ...out[0], offset: 0 });\n\tconst last = out[out.length - 1];\n\tif (last && last.offset < 1) out.push({ ...last, offset: 1 });\n\treturn out;\n}\n\n/**\n * Stops repeating the gradient's plain stops over periods `lo` … `hi` (integers, at most\n * `VECTOR_GRADIENT_MAX_PERIODS`), period `k` mirrored when `mirrored(k)`; offsets over 0–1 span\n * the whole range (vectorcraft `spread::expand`, which emulates `reflect` and `repeat` this way).\n */\nexport function periodicVectorGradientStops(stops: readonly VectorGradientStop[], lo: number, hi: number, mirrored: (k: number) => boolean): VectorGradientStop[] {\n\tconst period = wholePeriod(stops);\n\tconst back = reverseVectorGradient({ stops: period }).stops;\n\tconst n = Math.max(1, hi - lo);\n\tconst out: VectorGradientStop[] = [];\n\tfor (let j = 0; j < n; j++) {\n\t\tconst one = mirrored(lo + j) ? back : period;\n\t\tfor (const s of one) out.push({ ...s, offset: (j + s.offset) / n });\n\t}\n\treturn out;\n}\n\n/**\n * A linear or reflected gradient's spread as stop periods over `area` (node space), for\n * renderers (and combinations) without the spread mode: vectorcraft's SVG import emulates\n * `reflect` and `repeat` this way. Returns where the periods start and end and their stops, or\n * null when a linear gradient pads (nothing to repeat). Reflected gradients always get at least\n * the period either side of the start (the one before it mirrored). `from` / `to` are the\n * periods' ends along the frame's u axis (`matrix` maps the frame to node space).\n */\nexport function expandVectorGradientSpread(\n\tg: VectorGradient,\n\tbounds: VectorBounds | null | undefined,\n\tarea: VectorBounds\n): { start: Vec2; end: Vec2; from: number; to: number; matrix: VectorMatrix; stops: VectorGradientStop[]; capped: boolean } | null {\n\tconst m = vectorGradientMatrix(g, bounds);\n\tconst inv = invert(m);\n\tconst spread = g.spread ?? 'pad';\n\tconst reflected = g.type === 'reflected';\n\tif (!inv || (!reflected && spread === 'pad')) return null;\n\tconst odd = (k: number) => ((k % 2) + 2) % 2 === 1;\n\tconst stops = expandVectorGradientStops(g);\n\tconst along = (a: number, b: number, mirrored: (k: number) => boolean, capped = false) => ({\n\t\tstart: apply(m, { x: a, y: 0 }),\n\t\tend: apply(m, { x: b, y: 0 }),\n\t\tfrom: a,\n\t\tto: b,\n\t\tmatrix: m,\n\t\tstops: periodicVectorGradientStops(stops, a, b, mirrored),\n\t\tcapped\n\t});\n\tif (reflected && spread === 'pad') return along(-1, 1, (k) => k < 0);\n\tconst us = boxCorners(area).map((c) => apply(inv, c).x);\n\tlet lo = Math.floor(Math.min(...us));\n\tlet hi = Math.ceil(Math.max(...us));\n\tif (reflected) {\n\t\tlo = Math.min(lo, -1);\n\t\thi = Math.max(hi, 1);\n\t}\n\thi = Math.max(hi, lo + 1);\n\tif (!reflected && lo >= 0 && hi <= 1) return null;\n\tconst capped = hi - lo > VECTOR_GRADIENT_MAX_PERIODS;\n\tif (capped) {\n\t\tlo = Math.max(lo, -VECTOR_GRADIENT_MAX_PERIODS / 2);\n\t\thi = lo + VECTOR_GRADIENT_MAX_PERIODS;\n\t}\n\t// Period k covers u in [k, k + 1]. A reflected gradient runs |u|: before the start, periods run backwards.\n\tconst mirrored = reflected\n\t\t? (k: number) => {\n\t\t\t\tconst j = k >= 0 ? k : -k - 1;\n\t\t\t\tconst own = spread === 'reflect' && odd(j);\n\t\t\t\treturn k < 0 ? !own : own;\n\t\t\t}\n\t\t: (k: number) => spread === 'reflect' && odd(k);\n\treturn along(lo, hi, mirrored, capped);\n}\n\nconst boxCorners = (b: VectorBounds): Vec2[] => [\n\t{ x: b.x, y: b.y },\n\t{ x: b.x + b.width, y: b.y },\n\t{ x: b.x + b.width, y: b.y + b.height },\n\t{ x: b.x, y: b.y + b.height }\n];\n\n// ---------------------------------------------------------------------------\n// SVG paint servers\n// ---------------------------------------------------------------------------\n\n/**\n * A `<pattern>` paint server in user space: draw `gradients` (inside it or in `<defs>`) and then\n * each tile (`d` filled with `fill`) in order, all in the painted element's user space (the\n * `viewBox` in `attrs` matches the tile, so keep it). Conic and diamond gradients, and gradients along\n * or across a stroke, take this form.\n */\nexport interface VectorSvgPatternDef {\n\tid: string;\n\ttype: 'pattern';\n\t/** `x`, `y`, `width`, `height`, `patternUnits=\"userSpaceOnUse\"` and the matching `viewBox`. */\n\tattrs: Record<string, string | number>;\n\tgradients: SvgGradientDef[];\n\ttiles: { d: string; fill: string }[];\n}\n\n/** A paint server for `fill` / `stroke`: a gradient element or a pattern of gradient tiles. */\nexport type VectorPaintDef = SvgGradientDef | VectorSvgPatternDef;\n\nexport interface VectorGradientSvgOptions {\n\t/** The shape's box in its own space (for `objectBoundingBox` gradients). */\n\tbounds?: VectorBounds | null;\n\t/** The area the paint must cover (pattern tiles, spread periods); default `bounds` padded by 1. */\n\tarea?: VectorBounds | null;\n\t/** Wedges of a conic gradient (default 72: 5° each). */\n\twedges?: number;\n\tprecision?: number;\n}\n\nconst toSvgStops = (stops: readonly VectorGradientStop[]): SvgGradientStop[] =>\n\tstops.map((s) => ({ offset: `${fmt(s.offset * 100, 3)}%`, stopColor: s.color, ...(s.opacity !== undefined && s.opacity !== 1 ? { stopOpacity: Math.round(s.opacity * 1e4) / 1e4 } : {}) }));\n\nconst polygonD = (pts: readonly Vec2[], precision: number) => `M${pts.map((p) => `${fmt(p.x, precision)} ${fmt(p.y, precision)}`).join('L')}Z`;\n\nconst padBounds = (b: VectorBounds, pad: number): VectorBounds => ({ x: b.x - pad, y: b.y - pad, width: b.width + 2 * pad, height: b.height + 2 * pad });\n\n/** The area a gradient on a shape with box `bounds` paints (the box padded by 1). */\nexport const vectorGradientArea = (bounds: VectorBounds | null | undefined): VectorBounds => padBounds(bounds ?? { x: 0, y: 0, width: 1, height: 1 }, 1);\n\n/** The `gradientTransform` attribute of matrix `m` (none for the identity). */\nconst transformAttr = (m: VectorMatrix): Record<string, string> => (isIdentity(m) ? {} : { gradientTransform: fmtMatrix(m) });\n\n/** Smallest stretch of a matrix (the least singular value). */\nfunction minScale(m: VectorMatrix): number {\n\tconst [a, b, c, d] = m;\n\tconst p = a * a + b * b;\n\tconst q = c * c + d * d;\n\tconst r = a * c + b * d;\n\treturn Math.sqrt(Math.max(0, (p + q - Math.sqrt((p - q) ** 2 + 4 * r * r)) / 2));\n}\n\n/**\n * A user-space pattern tile over `area`. Without a `viewBox` SVG puts the content's origin at the\n * tile's corner; the matching `viewBox` keeps the content in the painted element's user space.\n */\nexport function patternAttrs(area: VectorBounds): Record<string, string> {\n\tconst [x, y, w, h] = [area.x, area.y, Math.max(area.width, 1e-3), Math.max(area.height, 1e-3)].map((n) => fmt(n, 3));\n\treturn { patternUnits: 'userSpaceOnUse', x, y, width: w, height: h, viewBox: `${x} ${y} ${w} ${h}` };\n}\n\n/** A pattern def over `area` with the given tiles. */\nfunction patternDef(id: string, area: VectorBounds, gradients: SvgGradientDef[], tiles: { d: string; fill: string }[]): VectorSvgPatternDef {\n\treturn {\n\t\tid,\n\t\ttype: 'pattern',\n\t\tattrs: patternAttrs(area),\n\t\tgradients,\n\t\ttiles\n\t};\n}\n\n/**\n * The SVG paint servers of a gradient on a shape: the first def's id is `id` (reference it as\n * `url(#id)`), any others are what it uses. All are in the user space of the element painted\n * (the node's own space).\n */\nexport function vectorGradientSvgDefs(g: VectorGradient, id: string, options: VectorGradientSvgOptions = {}): VectorPaintDef[] {\n\tconst precision = options.precision ?? 3;\n\tconst bounds = options.bounds ?? null;\n\tconst area = options.area ?? vectorGradientArea(bounds);\n\tconst spread = g.spread && VECTOR_GRADIENT_SPREADS.includes(g.spread) ? g.spread : 'pad';\n\tconst stops = expandVectorGradientStops(g);\n\tconst p = vectorGradientPoints(g);\n\tconst unitsM = vectorGradientUnitsMatrix(g, bounds);\n\tconst frameM = mul(framePointsMatrix(p), unitsM);\n\tconst spreadAttr = (s: VectorGradientSpread): Record<string, string> => (s === 'pad' ? {} : { spreadMethod: s });\n\tconst base = { gradientUnits: 'userSpaceOnUse' };\n\n\tswitch (g.type) {\n\t\tcase 'linear':\n\t\tcase 'reflected': {\n\t\t\tif (g.type === 'reflected' && spread !== 'reflect') {\n\t\t\t\t// Pad: mirrored stops over [2·start − end, end]; repeat: periods over the area, the ones before the start mirrored.\n\t\t\t\tconst ex = expandVectorGradientSpread(g, bounds, area);\n\t\t\t\tconst [a, b] = ex ? [ex.from, ex.to] : [-1, 1];\n\t\t\t\tconst st = ex?.stops ?? periodicVectorGradientStops(stops, -1, 1, (k) => k < 0);\n\t\t\t\treturn [{ id, type: 'linear', attrs: { ...base, x1: a, y1: 0, x2: b, y2: 0, gradientTransform: fmtMatrix(frameM) }, stops: toSvgStops(st) }];\n\t\t\t}\n\t\t\tconst u = sub(p.end, p.start);\n\t\t\tconst w = sub(p.width, p.start);\n\t\t\tconst square = Math.abs(dot(u, w)) <= 1e-9 * Math.max(dot(u, u), dot(w, w), 1e-18);\n\t\t\t// A skewed frame (a userSpaceOnUse gradient sheared with its shape) needs the frame as the transform.\n\t\t\tconst coords: Record<string, string | number> = square ? { x1: fmt(p.start.x, 6), y1: fmt(p.start.y, 6), x2: fmt(p.end.x, 6), y2: fmt(p.end.y, 6), ...transformAttr(unitsM) } : { x1: 0, y1: 0, x2: 1, y2: 0, gradientTransform: fmtMatrix(frameM) };\n\t\t\treturn [{ id, type: 'linear', attrs: { ...base, ...coords, ...spreadAttr(spread) }, stops: toSvgStops(stops) }];\n\t\t}\n\t\tcase 'radial': {\n\t\t\tconst f = unitFocal(g);\n\t\t\tconst attrs: Record<string, string | number> = { ...base, cx: 0, cy: 0, r: 1, gradientTransform: fmtMatrix(frameM), ...spreadAttr(spread) };\n\t\t\tif (f && (Math.abs(f.f.x) > 1e-9 || Math.abs(f.f.y) > 1e-9)) {\n\t\t\t\tattrs.fx = fmt(f.f.x, 6);\n\t\t\t\tattrs.fy = fmt(f.f.y, 6);\n\t\t\t}\n\t\t\tif (f && f.fr > 0) attrs.fr = fmt(f.fr, 6);\n\t\t\treturn [{ id, type: 'radial', attrs, stops: toSvgStops(stops) }];\n\t\t}\n\t\tcase 'diamond': {\n\t\t\tconst inv = invert(frameM);\n\t\t\tif (!inv) return [patternDef(id, area, [], [{ d: polygonD(boxCorners(area), precision), fill: stops[stops.length - 1]?.color ?? 'none' }])];\n\t\t\tconst reach = 2 * Math.max(...boxCorners(area).map((c) => {\n\t\t\t\tconst q = apply(inv, c);\n\t\t\t\treturn Math.abs(q.x) + Math.abs(q.y);\n\t\t\t})) + 1;\n\t\t\t// Each quadrant overlaps its neighbours by about 0.75 units, hiding anti-aliasing seams.\n\t\t\tconst delta = Math.min(0.05 * reach, 0.75 / Math.max(minScale(frameM), 1e-9));\n\t\t\tconst gradients: SvgGradientDef[] = [];\n\t\t\tconst tiles: { d: string; fill: string }[] = [];\n\t\t\t[[1, 1], [-1, 1], [-1, -1], [1, -1]].forEach(([sx, sy], k) => {\n\t\t\t\tconst gid = `${id}-q${k}`;\n\t\t\t\tgradients.push({ id: gid, type: 'linear', attrs: { ...base, x1: 0, y1: 0, x2: sx / 2, y2: sy / 2, gradientTransform: fmtMatrix(frameM), ...spreadAttr(spread) }, stops: toSvgStops(stops) });\n\t\t\t\tconst quad = [{ x: -delta * sx, y: -delta * sy }, { x: reach * sx, y: -delta * sy }, { x: -delta * sx, y: reach * sy }].map((q) => apply(frameM, q));\n\t\t\t\ttiles.push({ d: polygonD(quad, precision), fill: `url(#${gid})` });\n\t\t\t});\n\t\t\treturn [patternDef(id, area, gradients, tiles)];\n\t\t}\n\t\tcase 'conic':\n\t\t\treturn [conicPattern(id, frameM, stops, area, options.wedges ?? 72, precision)];\n\t}\n}\n\n/**\n * A conic gradient as thin wedges round the centre, each with a linear gradient across it\n * (along the tangent at its middle angle, exact on the unit circle of the frame: elsewhere a\n * point takes a colour from within its wedge). Stops inside a wedge keep sharp transitions.\n */\nfunction conicPattern(id: string, frameM: VectorMatrix, stops: readonly VectorGradientStop[], area: VectorBounds, wedges: number, precision: number): VectorSvgPatternDef {\n\tconst inv = invert(frameM);\n\tconst n = Math.max(8, Math.min(720, Math.round(wedges)));\n\tconst half = Math.PI / n;\n\tif (!inv || !stops.length) return patternDef(id, area, [], [{ d: polygonD(boxCorners(area), precision), fill: stops[0]?.color ?? 'none' }]);\n\tconst reach = (Math.max(...boxCorners(area).map((c) => Math.hypot(apply(inv, c).x, apply(inv, c).y))) + 1) / Math.cos(half);\n\tconst overlap = Math.min(half, 0.002);\n\tconst gradients: SvgGradientDef[] = [];\n\tconst tiles: { d: string; fill: string }[] = [];\n\tfor (let k = 0; k < n; k++) {\n\t\tconst a0 = (2 * Math.PI * k) / n;\n\t\tconst a1 = (2 * Math.PI * (k + 1)) / n;\n\t\tconst mid = (a0 + a1) / 2;\n\t\tconst tau = { x: -Math.sin(mid), y: Math.cos(mid) };\n\t\tconst s = Math.sin(half);\n\t\tconst at = (angle: number) => (Math.sin(angle - mid) / s + 1) / 2;\n\t\tconst t0 = k / n;\n\t\tconst t1 = (k + 1) / n;\n\t\tconst own: VectorGradientStop[] = [stopOf(0, samplePlainStops(stops, t0))];\n\t\tfor (const st of stops) if (st.offset > t0 && st.offset < t1) own.push({ ...st, offset: at(st.offset * 2 * Math.PI) });\n\t\town.push(stopOf(1, samplePlainStops(stops, t1)));\n\t\tconst gid = `${id}-w${k}`;\n\t\tgradients.push({\n\t\t\tid: gid,\n\t\t\ttype: 'linear',\n\t\t\tattrs: { gradientUnits: 'userSpaceOnUse', x1: fmt(-s * tau.x, 6), y1: fmt(-s * tau.y, 6), x2: fmt(s * tau.x, 6), y2: fmt(s * tau.y, 6), gradientTransform: fmtMatrix(frameM) },\n\t\t\tstops: toSvgStops(own)\n\t\t});\n\t\tconst from = a0 - (k === 0 ? 0 : overlap);\n\t\tconst pts = [{ x: 0, y: 0 }, { x: reach * Math.cos(from), y: reach * Math.sin(from) }, { x: reach * Math.cos(a1), y: reach * Math.sin(a1) }].map((q) => apply(frameM, q));\n\t\ttiles.push({ d: polygonD(pts, precision), fill: `url(#${gid})` });\n\t}\n\treturn patternDef(id, area, gradients, tiles);\n}\n\n// ---------------------------------------------------------------------------\n// Evaluating paint servers (tests, previews)\n// ---------------------------------------------------------------------------\n\nconst num = (v: string | number | undefined, fallback = 0) => {\n\tif (v === undefined) return fallback;\n\tconst n = typeof v === 'number' ? v : parseFloat(v);\n\treturn Number.isFinite(n) ? n : fallback;\n};\n\nconst parseMatrixAttr = (v: string | number | undefined): VectorMatrix => {\n\tconst m = typeof v === 'string' ? /^matrix\\(([^)]*)\\)$/.exec(v.trim()) : null;\n\tif (!m) return IDENTITY;\n\tconst n = m[1].split(/[\\s,]+/).map(Number);\n\treturn n.length === 6 && n.every(Number.isFinite) ? (n as unknown as VectorMatrix) : IDENTITY;\n};\n\nconst plainOf = (stops: readonly SvgGradientStop[]): VectorGradientStop[] =>\n\tstops.map((s) => ({ offset: parseFloat(s.offset) / (s.offset.endsWith('%') ? 100 : 1), color: s.stopColor, ...(s.stopOpacity !== undefined ? { opacity: s.stopOpacity } : {}) }));\n\n/** Point `p` inside a polygon tile (`M x y L … Z`), even-odd. */\nfunction inTile(d: string, p: Vec2): boolean {\n\tconst n = d.match(/-?[\\d.]+(?:e-?\\d+)?/gi)?.map(Number) ?? [];\n\tlet inside = false;\n\tfor (let i = 0, j = n.length - 2; i + 1 < n.length; j = i, i += 2) {\n\t\tconst [xi, yi, xj, yj] = [n[i], n[i + 1], n[j], n[j + 1]];\n\t\tif (yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi) inside = !inside;\n\t}\n\treturn inside;\n}\n\n/**\n * What a `userSpaceOnUse` paint server from `vectorGradientSvgDefs` paints at point `p` (straight\n * alpha, 0–1) the way an SVG renderer would: a gradient's stops interpolated in sRGB, a pattern's\n * topmost tile containing `p`. Null for a point no tile covers.\n */\nexport function sampleVectorPaintDefAt(defs: readonly VectorPaintDef[], id: string, p: Vec2): VectorRgba | null {\n\tconst all = new Map<string, VectorPaintDef>();\n\tfor (const d of defs) {\n\t\tall.set(d.id, d);\n\t\tif (d.type === 'pattern') for (const gd of d.gradients) all.set(gd.id, gd);\n\t}\n\tconst paint = (ref: string, q: Vec2): VectorRgba | null => {\n\t\tconst url = /^url\\(#(.+)\\)$/.exec(ref);\n\t\tif (!url) return parseVectorColor(ref);\n\t\tconst def = all.get(url[1]);\n\t\tif (!def) return null;\n\t\tif (def.type === 'pattern') {\n\t\t\tfor (let i = def.tiles.length - 1; i >= 0; i--) if (inTile(def.tiles[i].d, q)) return paint(def.tiles[i].fill, q);\n\t\t\treturn null;\n\t\t}\n\t\tconst inv = invert(parseMatrixAttr(def.attrs.gradientTransform)) ?? IDENTITY;\n\t\tconst local = apply(inv, q);\n\t\tconst spread = (def.attrs.spreadMethod as VectorGradientSpread | undefined) ?? 'pad';\n\t\tlet t: number;\n\t\tif (def.type === 'radial') {\n\t\t\tconst r = num(def.attrs.r, 0.5);\n\t\t\tconst c = { x: num(def.attrs.cx), y: num(def.attrs.cy) };\n\t\t\tconst f = { x: num(def.attrs.fx, c.x), y: num(def.attrs.fy, c.y) };\n\t\t\tconst u = { x: (local.x - c.x) / r, y: (local.y - c.y) / r };\n\t\t\tt = vectorGradientFocalParam(u, { x: (f.x - c.x) / r, y: (f.y - c.y) / r }, num(def.attrs.fr) / r);\n\t\t} else {\n\t\t\tconst a = { x: num(def.attrs.x1), y: num(def.attrs.y1) };\n\t\t\tconst b = { x: num(def.attrs.x2, 1), y: num(def.attrs.y2) };\n\t\t\tconst v = sub(b, a);\n\t\t\tconst l2 = dot(v, v);\n\t\t\tt = l2 > 1e-18 ? dot(sub(local, a), v) / l2 : 0;\n\t\t}\n\t\treturn samplePlainStops(plainOf(def.stops), applyVectorGradientSpread(t, spread));\n\t};\n\treturn paint(`url(#${id})`, p);\n}\n\n// ---------------------------------------------------------------------------\n// Sanitizing (agents, import)\n// ---------------------------------------------------------------------------\n\nconst readPoint = (v: unknown): Vec2 | undefined => (finitePoint(v) ? { x: v.x, y: v.y } : undefined);\n\n/**\n * A gradient from untrusted input (agent commands, SVG data attributes), or null when it is not\n * one: unknown fields dropped, numbers clamped, at least one stop.\n */\nexport function sanitizeVectorGradient(input: unknown): VectorGradient | null {\n\tif (!input || typeof input !== 'object') return null;\n\tconst r = input as Record<string, unknown>;\n\tif (!VECTOR_GRADIENT_TYPES.includes(r.type as VectorGradientType) || !Array.isArray(r.stops)) return null;\n\tconst stops: VectorGradientStop[] = [];\n\tfor (const s of r.stops as unknown[]) {\n\t\tif (!s || typeof s !== 'object') continue;\n\t\tconst x = s as Record<string, unknown>;\n\t\tif (typeof x.color !== 'string' || typeof x.offset !== 'number' || !Number.isFinite(x.offset)) continue;\n\t\tconst stop: VectorGradientStop = { offset: clamp(x.offset, 0, 1), color: x.color.slice(0, 100) };\n\t\tif (typeof x.opacity === 'number' && Number.isFinite(x.opacity) && x.opacity !== 1) stop.opacity = clamp(x.opacity, 0, 1);\n\t\tif (typeof x.midpoint === 'number' && Number.isFinite(x.midpoint) && Math.abs(x.midpoint - 0.5) > 1e-6) stop.midpoint = clamp(x.midpoint, 0.05, 0.95);\n\t\tstops.push(stop);\n\t}\n\tif (!stops.length) return null;\n\tconst g: VectorGradient = { type: r.type as VectorGradientType, stops };\n\tfor (const k of ['angle', 'cx', 'cy', 'r'] as const) if (typeof r[k] === 'number' && Number.isFinite(r[k])) g[k] = r[k] as number;\n\tconst start = readPoint(r.start);\n\tconst end = readPoint(r.end);\n\tif (start && end) {\n\t\tg.start = start;\n\t\tg.end = end;\n\t\tconst width = readPoint(r.width);\n\t\tif (width) g.width = width;\n\t\tif (r.units === 'userSpaceOnUse' || r.units === 'objectBoundingBox') g.units = r.units;\n\t}\n\tconst focal = readPoint(r.focal);\n\tif (focal && g.type === 'radial') g.focal = focal;\n\tif (typeof r.focalRadius === 'number' && r.focalRadius > 0 && g.type === 'radial') g.focalRadius = clamp(r.focalRadius, 0, 0.99);\n\tif (VECTOR_GRADIENT_SPREADS.includes(r.spread as VectorGradientSpread) && r.spread !== 'pad') g.spread = r.spread as VectorGradientSpread;\n\tif (VECTOR_GRADIENT_INTERPOLATIONS.includes(r.interpolation as VectorGradientInterpolation) && r.interpolation !== 'srgb') g.interpolation = r.interpolation as VectorGradientInterpolation;\n\tif (typeof r.smoothness === 'number' && r.smoothness > 0) g.smoothness = clamp(r.smoothness, 0, 1);\n\tif (VECTOR_STROKE_GRADIENT_MODES.includes(r.strokeMode as never) && r.strokeMode !== 'within') g.strokeMode = r.strokeMode as VectorGradient['strokeMode'];\n\treturn g;\n}\n\n/**\n * A gradient changed to style `type`, keeping its stops, colour settings and placement: explicit\n * points stay where they are (a linear end becomes a radius), shorthand is redone for the style.\n */\nexport function convertVectorGradient(g: VectorGradient, type: VectorGradientType): VectorGradient {\n\tif (g.type === type) return g;\n\tconst next: VectorGradient = { ...g, type };\n\tif (type !== 'radial') {\n\t\tdelete next.focal;\n\t\tdelete next.focalRadius;\n\t}\n\tif (!hasExplicitGradientGeometry(g)) {\n\t\t// Shorthand fields mean different things per style: drop the ones the new style reads differently.\n\t\tif (isCentredVectorGradient(type) !== isCentredVectorGradient(g.type)) {\n\t\t\tdelete next.angle;\n\t\t\tdelete next.cx;\n\t\t\tdelete next.cy;\n\t\t\tdelete next.r;\n\t\t}\n\t}\n\treturn next;\n}\n\n/** A default gradient for a paint that was a colour (or nothing): the colour fading to white. */\nexport function defaultVectorGradient(type: VectorGradientType = 'linear', from?: VectorPaint): VectorGradient {\n\tif (isVectorGradient(from)) return convertVectorGradient(from, type);\n\tconst colour = typeof from === 'string' && parseVectorColor(from) ? from : '#000000';\n\treturn { type, stops: [{ offset: 0, color: colour }, { offset: 1, color: '#ffffff' }] };\n}\n\n/**\n * Changes to a gradient (`vector.setGradient`, the gradient editor): any gradient field, plus\n * `aspect` (the secondary axis' length over the main one, square to it) and `reverse`. `focal` or\n * `width` set to null removes them.\n */\nexport interface VectorGradientPatch extends Partial<Omit<VectorGradient, 'focal' | 'width'>> {\n\tfocal?: Vec2 | null;\n\twidth?: Vec2 | null;\n\taspect?: number;\n\treverse?: boolean;\n}\n\n/** `g` with `patch` applied (a style change converts it first), or null when the result is not a gradient. */\nexport function applyVectorGradientPatch(g: VectorGradient, patch: VectorGradientPatch): VectorGradient | null {\n\tconst { aspect, reverse, type, ...fields } = patch;\n\tlet next: VectorGradient = type && type !== g.type ? convertVectorGradient(g, type) : { ...g };\n\tconst record = next as unknown as Record<string, unknown>;\n\tfor (const [k, v] of Object.entries(fields)) {\n\t\tif (v === undefined) continue;\n\t\tif (v === null) delete record[k];\n\t\telse record[k] = v;\n\t}\n\t// Explicit points replace the shorthand (and the other way round).\n\tif ((fields.start || fields.end) && !hasExplicitGradientGeometry(next)) {\n\t\tconst p = vectorGradientPoints({ ...g, start: undefined, end: undefined });\n\t\tnext.start = next.start ?? p.start;\n\t\tnext.end = next.end ?? p.end;\n\t}\n\tif (aspect !== undefined && Number.isFinite(aspect)) {\n\t\tconst e = explicitVectorGradient(next);\n\t\tconst u = sub(e.end!, e.start!);\n\t\tconst a = Math.abs(aspect) < 0.01 ? (aspect < 0 ? -0.01 : 0.01) : aspect;\n\t\tnext = { ...e, width: add(e.start!, scale(perp(u), a)) };\n\t}\n\tif (reverse) next = reverseVectorGradient(next);\n\tconst clean = sanitizeVectorGradient(next);\n\tif (!clean) return null;\n\tclean.stops = sortedVectorGradientStops(clean);\n\treturn clean;\n}\n","/**\n * Live corners: a radius stored on an anchor, drawn as a round, inverted or chamfered cut when\n * the path is turned into curves. The stored anchors never change; `expandVectorCorners` is the\n * one place the cut geometry is made, and `subpathsToBezier` calls it, so rendering,\n * hit-testing, bounds, booleans, stroke outlines and SVG export all see the same shape.\n *\n * Ported from ArtCraft (MIT OR Apache-2.0, see THIRD_PARTY_NOTICES.md):\n * - figcraft `crates/doc/src/vector.rs` — `VectorNetwork::rounded` / `corner` and `trim`: each\n * side is trimmed by arc length (the radius clamped so a cut takes at most half of each\n * side) and the round cut is a cubic with handles `4/3 · tan(φ/4) · ρ` along the sides'\n * directions at the cut, φ the turn between them and ρ the radius of the circle through\n * both ends. That is exact at any angle and works where a curve meets a corner.\n * - vectorcraft `crates/geom/src/corners.rs` — `path_corners`, `Corner` (setback, max radius,\n * bisector and radius change for the drag widget) and `cut_corners` (inverted round and\n * chamfer kinds, trimming curved sides between cuts, a closed subpath whose first corner is\n * cut starting where that cut ends).\n */\nimport type { Point } from '../canvas/types.js';\nimport { segmentDerivative, segmentLength, segmentParamAtLength, segmentPoint, subSegment } from '../canvas/pathops/bezier.js';\nimport type { VectorAnchor, VectorCornerKind, VectorSubpath, Vec2 } from './types.js';\n\nconst EPS = 1e-9;\n/** Sides closer than this to straight on (or folded back), as the sine of their angle, make no corner. */\nconst MIN_SINE = 1e-6;\n\nconst sub = (a: Vec2, b: Vec2): Vec2 => ({ x: a.x - b.x, y: a.y - b.y });\nconst add = (a: Vec2, b: Vec2): Vec2 => ({ x: a.x + b.x, y: a.y + b.y });\nconst mul = (a: Vec2, k: number): Vec2 => ({ x: a.x * k, y: a.y * k });\nconst dot = (a: Vec2, b: Vec2) => a.x * b.x + a.y * b.y;\nconst cross = (a: Vec2, b: Vec2) => a.x * b.y - a.y * b.x;\nconst len = (a: Vec2) => Math.hypot(a.x, a.y);\nconst pt = (p: Vec2): Point => [p.x, p.y];\nconst vec = (p: Point): Vec2 => ({ x: p[0], y: p[1] });\nconst onAnchor = (a: VectorAnchor, h: Vec2 | null | undefined) => !h || (Math.abs(h.x - a.x) < EPS && Math.abs(h.y - a.y) < EPS);\n\n/** A side of a corner: the segment from the corner's anchor to a neighbour, as a cubic leaving the corner. */\nclass Side {\n\treadonly curve: Point[];\n\treadonly straight: boolean;\n\treadonly length: number;\n\tconstructor(a: VectorAnchor, h1: Vec2 | null | undefined, h2: Vec2 | null | undefined, b: VectorAnchor) {\n\t\tthis.straight = onAnchor(a, h1) && onAnchor(b, h2);\n\t\tthis.curve = this.straight ? [pt(a), pt(b)] : [pt(a), pt(h1 ?? a), pt(h2 ?? b), pt(b)];\n\t\tthis.length = this.straight ? Math.hypot(b.x - a.x, b.y - a.y) : segmentLength(this.curve);\n\t}\n\n\t/** Unit direction the side leaves the corner in (towards its first control point off the corner). */\n\tdirection(): Vec2 | null {\n\t\tconst p0 = vec(this.curve[0]);\n\t\tfor (const q of this.curve.slice(1)) {\n\t\t\tconst d = sub(vec(q), p0);\n\t\t\tconst l = len(d);\n\t\t\tif (l > EPS) return mul(d, 1 / l);\n\t\t}\n\t\treturn null;\n\t}\n\n\t/** The point `s` along the side from the corner: its parameter, position and the direction the side runs on in there. */\n\tat(s: number, u: Vec2): { t: number; p: Vec2; dir: Vec2 } {\n\t\tif (this.straight) {\n\t\t\tconst t = this.length > EPS ? Math.min(1, Math.max(0, s / this.length)) : 0;\n\t\t\treturn { t, p: add(vec(this.curve[0]), mul(u, s)), dir: u };\n\t\t}\n\t\tconst t = segmentParamAtLength(this.curve, s, this.length);\n\t\tconst d = vec(segmentDerivative(this.curve, t));\n\t\tconst l = len(d);\n\t\treturn { t, p: vec(segmentPoint(this.curve, t)), dir: l > EPS ? mul(d, 1 / l) : u };\n\t}\n}\n\n/**\n * A corner a live radius can cut: an anchor that is not smooth, where two sides (straight or\n * curved) meet at an angle. Positions are in the path node's own space.\n */\nexport interface VectorCornerInfo {\n\tsubpath: number;\n\tindex: number;\n\t/** Where the sides meet. */\n\tat: Vec2;\n\t/** Unit directions the sides leave the corner in: towards the previous anchor, then the next one. */\n\tu: Vec2;\n\tv: Vec2;\n\t/** The sides' lengths (along curves), in the same order. */\n\tlu: number;\n\tlv: number;\n\t/** Cosine and sine of the angle between the sides. */\n\tcos: number;\n\tsin: number;\n\t/** The angle between the sides in degrees (90 at a rectangle's corners). */\n\tangle: number;\n\t/** The largest radius that fits: its cut then reaches halfway along the shorter side. */\n\tmaxRadius: number;\n}\n\ninterface CornerData extends VectorCornerInfo {\n\tsideU: Side;\n\tsideV: Side;\n}\n\nfunction cornerOf(sp: VectorSubpath, s: number, i: number): CornerData | null {\n\tconst n = sp.anchors.length;\n\tif (n < 2 || (!sp.closed && (i === 0 || i === n - 1))) return null;\n\tif (n === 2 && sp.closed) return null;\n\tconst a = sp.anchors[i];\n\tif (a.type === 'smooth' || a.type === 'symmetric') return null;\n\tconst prev = sp.anchors[(i - 1 + n) % n];\n\tconst next = sp.anchors[(i + 1) % n];\n\tconst sideU = new Side(a, a.in, prev.out, prev);\n\tconst sideV = new Side(a, a.out, next.in, next);\n\tconst u = sideU.direction();\n\tconst v = sideV.direction();\n\tif (!u || !v) return null;\n\tconst lu = sideU.length;\n\tconst lv = sideV.length;\n\tif (!(lu > EPS && lv > EPS) || !Number.isFinite(lu) || !Number.isFinite(lv)) return null;\n\tconst cos = Math.max(-1, Math.min(1, dot(u, v)));\n\tconst sin = Math.abs(cross(u, v));\n\tif (sin <= MIN_SINE) return null;\n\treturn { subpath: s, index: i, at: { x: a.x, y: a.y }, u, v, lu, lv, cos, sin, angle: (Math.atan2(sin, cos) * 180) / Math.PI, maxRadius: ((Math.min(lu, lv) / 2) * sin) / (1 + cos), sideU, sideV };\n}\n\nconst strip = ({ sideU: _u, sideV: _v, ...info }: CornerData): VectorCornerInfo => info;\n\n/** The corner at anchor `index` of a subpath, or null when the anchor makes none (an open end, smooth, or straight on). */\nexport function vectorCornerAt(sp: VectorSubpath, index: number, subpath = 0): VectorCornerInfo | null {\n\tconst c = cornerOf(sp, subpath, index);\n\treturn c ? strip(c) : null;\n}\n\n/** Every corner of the subpaths that a live radius can cut, in anchor order. */\nexport function vectorCorners(subpaths: readonly VectorSubpath[]): VectorCornerInfo[] {\n\tconst out: VectorCornerInfo[] = [];\n\tsubpaths.forEach((sp, s) => {\n\t\tfor (let i = 0; i < sp.anchors.length; i++) {\n\t\t\tconst c = cornerOf(sp, s, i);\n\t\t\tif (c) out.push(strip(c));\n\t\t}\n\t});\n\treturn out;\n}\n\n/** How far from the corner a round cut of radius `r` meets each side: r / tan(angle / 2). */\nexport const cornerSetback = (c: Pick<VectorCornerInfo, 'cos' | 'sin'>, r: number) => (r * (1 + c.cos)) / c.sin;\n\n/** `radius` clamped to what the corner can draw (0 for negative or non-finite radii). */\nexport const fitCornerRadius = (c: Pick<VectorCornerInfo, 'maxRadius'>, radius: number) => (Number.isFinite(radius) ? Math.min(Math.max(0, radius), c.maxRadius) : 0);\n\n/** The point `k` along the corner's bisector, measured in (u + v): a round cut of radius r is centred at k = r / sin. */\nexport const cornerBisectorPoint = (c: VectorCornerInfo, k: number): Vec2 => add(c.at, mul(add(c.u, c.v), k));\n\n/** How much the radius grows when the round cut's centre moves by `d` (its part along the bisector, in radius units). */\nexport const cornerRadiusChange = (c: VectorCornerInfo, d: Vec2) => (dot(d, add(c.u, c.v)) * c.sin) / (2 + 2 * c.cos);\n\n/** Handle length per unit radius of a cubic arc spanning the angle whose cosine is `cos`: 4/3 · tan(angle / 4). */\nfunction arcHandle(cos: number): number {\n\tconst c = Math.max(-1, Math.min(1, cos));\n\tconst sinHalf = Math.sqrt(Math.max(0, (1 - c) / 2));\n\tconst cosHalf = Math.sqrt(Math.max(0, (1 + c) / 2));\n\treturn ((4 / 3) * sinHalf) / (1 + cosHalf);\n}\n\n/** The unit direction square to unit `a`, on `b`'s side. */\nfunction squareTo(a: Vec2, b: Vec2): Vec2 {\n\tconst w = sub(b, mul(a, dot(a, b)));\n\tconst l = len(w);\n\treturn l > EPS ? mul(w, 1 / l) : { x: 0, y: 0 };\n}\n\ninterface Cut {\n\t/** Parameters along each side (from the corner) where the cut meets it. */\n\ttu: number;\n\ttv: number;\n\tstart: Vec2;\n\t/** Handles of the cut's own segment (absent for a chamfer). */\n\tout: Vec2 | null;\n\tin: Vec2 | null;\n\tend: Vec2;\n}\n\nfunction cutCorner(c: CornerData, radius: number, kind: VectorCornerKind): Cut | null {\n\t// figcraft `corner`: trim each side by arc length, at most half of it.\n\tconst s = Math.min(cornerSetback(c, radius), Math.min(c.lu, c.lv) / 2);\n\tif (!(s > EPS)) return null;\n\tconst a = c.sideU.at(s, c.u);\n\tconst b = c.sideV.at(s, c.v);\n\tif (kind === 'chamfer') return { tu: a.t, tv: b.t, start: a.p, out: null, in: null, end: b.p };\n\tif (kind === 'inverted') {\n\t\t// vectorcraft `cut`: an arc centred on the corner through both ends, spanning the corner's angle.\n\t\tconst h = s * arcHandle(dot(a.dir, b.dir));\n\t\treturn { tu: a.t, tv: b.t, start: a.p, out: add(a.p, mul(squareTo(a.dir, b.dir), h)), in: add(b.p, mul(squareTo(b.dir, a.dir), h)), end: b.p };\n\t}\n\t// Round: d1, d2 point back towards the corner at the cut's ends; φ is the turn between them\n\t// and ρ the radius of the circle through both ends.\n\tconst d1 = mul(a.dir, -1);\n\tconst d2 = mul(b.dir, -1);\n\tconst turn = Math.acos(Math.max(-1, Math.min(1, -dot(d1, d2))));\n\tconst chord = len(sub(b.p, a.p));\n\tconst k = turn < 1e-9 ? chord / 3 : (((4 / 3) * Math.tan(turn / 4) * chord) / (2 * Math.sin(turn / 2)));\n\tif (!Number.isFinite(k)) return null;\n\treturn { tu: a.t, tv: b.t, start: a.p, out: add(a.p, mul(d1, k)), in: add(b.p, mul(d2, k)), end: b.p };\n}\n\nconst radiusOf = (a: VectorAnchor) => {\n\tconst r = a.corner?.radius;\n\treturn typeof r === 'number' && r > 0 && Number.isFinite(r) ? r : 0;\n};\n\n/** True when any anchor of the subpaths has a live corner radius. */\nexport function hasVectorCorners(subpaths: readonly VectorSubpath[]): boolean {\n\treturn subpaths.some((sp) => sp.anchors.some((a) => radiusOf(a) > 0));\n}\n\n/** One subpath with its corners cut; `sources[i]` is the stored anchor each output anchor comes from. */\nexport function expandVectorCornersOf(sp: VectorSubpath, subpath = 0): { subpath: VectorSubpath; sources: number[] } {\n\tconst n = sp.anchors.length;\n\tconst cuts: (Cut | null)[] = sp.anchors.map((a, i) => {\n\t\tconst r = radiusOf(a);\n\t\tif (!r) return null;\n\t\tconst c = cornerOf(sp, subpath, i);\n\t\tif (!c) return null;\n\t\tconst fitted = fitCornerRadius(c, r);\n\t\treturn fitted > EPS ? cutCorner(c, fitted, a.corner?.kind ?? 'round') : null;\n\t});\n\tif (!cuts.some(Boolean)) return { subpath: { closed: sp.closed, anchors: sp.anchors.map(plain) }, sources: sp.anchors.map((_, i) => i) };\n\t// Each segment from anchor j trimmed to what the cuts at its ends leave: its handles, or\n\t// undefined when it is untouched (null handles: a straight segment).\n\tconst trimmed: ({ out: Vec2 | null; in: Vec2 | null } | undefined)[] = sp.anchors.map((a, j) => {\n\t\tconst k = (j + 1) % n;\n\t\tif (k === 0 && !sp.closed) return undefined;\n\t\tconst b = sp.anchors[k];\n\t\tconst from = cuts[j];\n\t\tconst to = cuts[k];\n\t\tif (!from && !to) return undefined;\n\t\tif (onAnchor(a, a.out) && onAnchor(b, b.in)) return { out: null, in: null };\n\t\tconst s0 = from ? from.tv : 0;\n\t\tconst s1 = to ? 1 - to.tu : 1;\n\t\tconst seg: Point[] = [pt(a), pt(a.out ?? a), pt(b.in ?? b), pt(b)];\n\t\tconst part = subSegment(seg, s0, Math.max(s0, s1));\n\t\treturn { out: vec(part[1]), in: vec(part[2]) };\n\t});\n\tconst anchors: VectorAnchor[] = [];\n\tconst sources: number[] = [];\n\tlet closing: VectorAnchor | null = null;\n\tconst handleIn = (i: number, a: VectorAnchor) => {\n\t\tconst t = trimmed[(i - 1 + n) % n];\n\t\treturn t ? t.in : (a.in ?? null);\n\t};\n\tconst handleOut = (i: number, a: VectorAnchor) => {\n\t\tconst t = trimmed[i];\n\t\treturn t ? t.out : (a.out ?? null);\n\t};\n\tsp.anchors.forEach((a, i) => {\n\t\tconst c = cuts[i];\n\t\tif (!c) {\n\t\t\tanchors.push(withHandles({ x: a.x, y: a.y, ...(a.type ? { type: a.type } : {}) }, handleIn(i, a), handleOut(i, a)));\n\t\t\tsources.push(i);\n\t\t\treturn;\n\t\t}\n\t\tconst start = withHandles({ x: c.start.x, y: c.start.y }, handleIn(i, a), c.out);\n\t\tconst end = withHandles({ x: c.end.x, y: c.end.y }, c.in, handleOut(i, a));\n\t\tif (i === 0 && sp.closed) closing = start;\n\t\telse {\n\t\t\tanchors.push(start);\n\t\t\tsources.push(i);\n\t\t}\n\t\tanchors.push(end);\n\t\tsources.push(i);\n\t});\n\tif (closing) {\n\t\tanchors.push(closing);\n\t\tsources.push(0);\n\t}\n\treturn { subpath: { closed: sp.closed, anchors }, sources };\n}\n\nfunction withHandles(a: VectorAnchor, hin: Vec2 | null, hout: Vec2 | null): VectorAnchor {\n\tif (hin && !onAnchor(a, hin)) a.in = { x: hin.x, y: hin.y };\n\tif (hout && !onAnchor(a, hout)) a.out = { x: hout.x, y: hout.y };\n\treturn a;\n}\n\nfunction plain(a: VectorAnchor): VectorAnchor {\n\tif (!('corner' in a)) return a;\n\tconst { corner: _c, ...rest } = a;\n\treturn rest;\n}\n\n/**\n * The subpaths with every live corner cut (round, inverted or chamfer), each radius no larger\n * than fits. A cut corner becomes two anchors and curved sides are trimmed to what the cuts\n * leave of them. Subpaths without corners come back unchanged.\n */\nexport function expandVectorCorners(subpaths: readonly VectorSubpath[]): VectorSubpath[] {\n\tif (!hasVectorCorners(subpaths)) return subpaths as VectorSubpath[];\n\treturn subpaths.map((sp, s) => (sp.anchors.some((a) => radiusOf(a) > 0) ? expandVectorCornersOf(sp, s).subpath : sp));\n}\n\n/**\n * Sets (or clears, with radius 0) the live corner of anchors. `kind` keeps each anchor's own\n * kind when omitted. Anchors that make no corner keep the radius too, so it applies once they do.\n */\nexport function setAnchorCorner(a: VectorAnchor, radius: number, kind?: VectorCornerKind): VectorAnchor {\n\tconst { corner, ...rest } = a;\n\tconst r = Number.isFinite(radius) ? Math.max(0, radius) : 0;\n\tconst k = kind ?? corner?.kind;\n\tif (r <= 0 && !kind) return rest;\n\treturn { ...rest, corner: { radius: r, ...(k && k !== 'round' ? { kind: k } : {}) } };\n}\n\n/** The next corner kind (Alt-clicking a corner widget cycles round → inverted → chamfer). */\nexport function nextVectorCornerKind(kind: VectorCornerKind | undefined): VectorCornerKind {\n\treturn kind === 'inverted' ? 'chamfer' : kind === 'chamfer' ? 'round' : 'inverted';\n}\n\n/** Every anchor of a subpath given the same live corner (radius 0 clears them). */\nexport function setSubpathCorners(sp: VectorSubpath, radius: number, kind?: VectorCornerKind): VectorSubpath {\n\treturn { closed: sp.closed, anchors: sp.anchors.map((a) => setAnchorCorner(a, radius, kind)) };\n}\n","/**\n * Variable-width stroke profiles: stroke widths at positions along a path (each subpath on its\n * own), interpolated smoothly and held flat before the first and after the last point. Each\n * point has a left and a right side scale (looking along the path on screen, y down), so a\n * stroke can grow on one side only.\n *\n * Ported from ArtCraft (MIT OR Apache-2.0, see THIRD_PARTY_NOTICES.md):\n * - figcraft `crates/doc/src/props.rs` — `WidthProfile` / `WidthPoint`: sanitising, side\n * scales, inserting and moving points, and the monotone cubic (Fritsch–Carlson) interpolation\n * that never overshoots its neighbouring points.\n * - vectorcraft `crates/doc/src/appearance.rs` — `WidthProfile` as `(t, left, right)` points,\n * discontinuous points (two points at one `t` make a step) and the profile presets.\n * - effectcraft `crates/path/src/varstroke.rs` — `width_factor`: the parametric Taper (lengths,\n * end widths, ease) and Wave (amount, cycles, phase) shapes, used here as profile generators.\n */\nimport type { VectorWidthPoint } from './types.js';\n\n/** The most points a profile keeps. */\nexport const VECTOR_WIDTH_PROFILE_MAX_POINTS = 64;\n/** The widest a side can be, as a multiple of the stroke width. */\nexport const VECTOR_WIDTH_PROFILE_MAX_SCALE = 1000;\n/** Positions closer than this are the same place (the two sides of a discontinuous point). */\nexport const VECTOR_WIDTH_SAME_T = 1e-6;\n\n/** Built-in profiles. `uniform` is the plain stroke (no profile). */\nexport type VectorWidthPreset = 'uniform' | 'taperStart' | 'taperEnd' | 'taperBoth' | 'bulge' | 'pinch' | 'teardrop' | 'wave' | 'pressure';\n\n/** One preset as a menu shows it. */\nexport interface VectorWidthPresetInfo {\n\tid: VectorWidthPreset;\n\tlabel: string;\n\tpoints: readonly VectorWidthPoint[];\n}\n\nconst sym = (t: number, s: number): VectorWidthPoint => ({ t, left: s, right: s });\n\n/** The built-in profiles, in menu order. */\nexport const VECTOR_WIDTH_PRESETS: readonly VectorWidthPresetInfo[] = [\n\t{ id: 'uniform', label: 'Uniform', points: [] },\n\t{ id: 'taperStart', label: 'Taper start', points: [sym(0, 0), sym(1, 1)] },\n\t{ id: 'taperEnd', label: 'Taper end', points: [sym(0, 1), sym(1, 0)] },\n\t{ id: 'taperBoth', label: 'Taper both', points: [sym(0, 0), sym(0.5, 1), sym(1, 0)] },\n\t// Full width at the ends, swelling to twice the width in the middle.\n\t{ id: 'bulge', label: 'Bulge', points: [sym(0, 1), sym(0.5, 2), sym(1, 1)] },\n\t// Full width at the ends, pinched to a quarter in the middle.\n\t{ id: 'pinch', label: 'Pinch', points: [sym(0, 1), sym(0.5, 0.25), sym(1, 1)] },\n\t// A round head a fifth of the way along, then a long taper to the end.\n\t{ id: 'teardrop', label: 'Teardrop', points: [sym(0, 0), sym(0.2, 1), sym(1, 0)] },\n\t// Two swells between narrow necks.\n\t{ id: 'wave', label: 'Wave', points: [sym(0, 0.3), sym(0.25, 1), sym(0.5, 0.3), sym(0.75, 1), sym(1, 0.3)] },\n\t// A pen stroke: a quick press, a slow lift.\n\t{ id: 'pressure', label: 'Pressure', points: [sym(0, 0.15), sym(0.12, 0.85), sym(0.45, 1), sym(0.8, 0.7), sym(1, 0.05)] }\n];\n\nexport const VECTOR_WIDTH_PRESET_IDS: readonly VectorWidthPreset[] = VECTOR_WIDTH_PRESETS.map((p) => p.id);\n\nconst clean = (v: unknown, max: number) => (typeof v === 'number' && Number.isFinite(v) ? Math.min(Math.max(v, 0), max) : null);\n\n/**\n * A profile as the outline uses it: finite points only, `t` in 0–1, side scales in 0–1000,\n * sorted by `t` (stable, so a step keeps its order), at most 64 points. Points missing a side\n * take the other side's scale.\n */\nexport function sanitizeWidthProfile(points: readonly Partial<VectorWidthPoint>[] | null | undefined): VectorWidthPoint[] {\n\tif (!Array.isArray(points)) return [];\n\tconst out: VectorWidthPoint[] = [];\n\tfor (const p of points) {\n\t\tif (!p || typeof p !== 'object') continue;\n\t\tconst t = clean(p.t, 1);\n\t\tconst l = clean(p.left, VECTOR_WIDTH_PROFILE_MAX_SCALE);\n\t\tconst r = clean(p.right, VECTOR_WIDTH_PROFILE_MAX_SCALE);\n\t\tif (t === null || (l === null && r === null)) continue;\n\t\tout.push({ t, left: l ?? r!, right: r ?? l! });\n\t\tif (out.length >= VECTOR_WIDTH_PROFILE_MAX_POINTS) break;\n\t}\n\treturn out.map((p, i) => [p, i] as const).sort((a, b) => a[0].t - b[0].t || a[1] - b[1]).map(([p]) => p);\n}\n\n/** True when the profile draws a plain stroke: no points, or every side at scale 1. */\nexport function isUniformWidthProfile(points: readonly VectorWidthPoint[] | null | undefined): boolean {\n\treturn !points?.length || points.every((p) => p.left === 1 && p.right === 1);\n}\n\n/** Slope of side `k` from point `i` to `i + 1` (0 when they share a position). */\nfunction secant(pts: readonly VectorWidthPoint[], i: number, k: 'left' | 'right'): number {\n\tconst a = pts[i];\n\tconst b = pts[i + 1];\n\treturn a && b && b.t - a.t > 1e-12 ? (b[k] - a[k]) / (b.t - a.t) : 0;\n}\n\n/** Fritsch–Carlson tangent: one-sided at the ends, the harmonic mean of the slopes inside, flat at local extremes. */\nfunction tangent(pts: readonly VectorWidthPoint[], i: number, k: 'left' | 'right'): number {\n\tconst last = pts.length - 1;\n\tif (i <= 0) return secant(pts, 0, k);\n\tif (i >= last) return secant(pts, last - 1, k);\n\tconst d0 = secant(pts, i - 1, k);\n\tconst d1 = secant(pts, i, k);\n\treturn d0 * d1 <= 0 ? 0 : (2 * d0 * d1) / (d0 + d1);\n}\n\nfunction interp(pts: readonly VectorWidthPoint[], t: number, k: 'left' | 'right'): number {\n\tconst first = pts[0];\n\tconst last = pts[pts.length - 1];\n\tif (!(t > first.t)) return first[k];\n\tif (t >= last.t) return last[k];\n\t// The last point at or before t (the second of a step at exactly t).\n\tlet i = 0;\n\twhile (i + 1 < pts.length && pts[i + 1].t <= t) i++;\n\tconst a = pts[i];\n\tconst b = pts[i + 1];\n\tconst h = b.t - a.t;\n\tif (h <= 1e-12) return b[k];\n\tconst u = (t - a.t) / h;\n\tconst u2 = u * u;\n\tconst u3 = u2 * u;\n\tconst w = (2 * u3 - 3 * u2 + 1) * a[k] + (u3 - 2 * u2 + u) * h * tangent(pts, i, k) + (3 * u2 - 2 * u3) * b[k] + (u3 - u2) * h * tangent(pts, i + 1, k);\n\treturn Math.max(0, w);\n}\n\n/**\n * The left and right side scales at `t` (0–1 along the subpath by length). Each side is\n * interpolated on its own with a monotone cubic, so the width never overshoots its points;\n * a profile without points is uniform (1, 1).\n */\nexport function widthProfileAt(points: readonly VectorWidthPoint[] | null | undefined, t: number): { left: number; right: number } {\n\tif (!points?.length) return { left: 1, right: 1 };\n\treturn { left: interp(points, t, 'left'), right: interp(points, t, 'right') };\n}\n\n/** The largest side scale (the interpolation never exceeds its points); 1 without a profile. */\nexport function widthProfileMaxScale(points: readonly VectorWidthPoint[] | null | undefined): number {\n\tif (!points?.length) return 1;\n\treturn points.reduce((m, p) => Math.max(m, p.left, p.right), 0);\n}\n\n/** The points of a preset (empty for `uniform`), or null for an unknown id. */\nexport function widthProfilePreset(id: string): VectorWidthPoint[] | null {\n\tconst p = VECTOR_WIDTH_PRESETS.find((q) => q.id === id);\n\treturn p ? p.points.map((q) => ({ ...q })) : null;\n}\n\n/** The preset a profile matches: `uniform` without one (or all at scale 1), `custom` when none matches. */\nexport function widthProfilePresetId(points: readonly VectorWidthPoint[] | null | undefined): VectorWidthPreset | 'custom' {\n\tif (isUniformWidthProfile(points)) return 'uniform';\n\tconst same = (a: readonly VectorWidthPoint[], b: readonly VectorWidthPoint[]) =>\n\t\ta.length === b.length && a.every((p, i) => Math.abs(p.t - b[i].t) < 1e-9 && Math.abs(p.left - b[i].left) < 1e-9 && Math.abs(p.right - b[i].right) < 1e-9);\n\treturn VECTOR_WIDTH_PRESETS.find((p) => p.points.length && same(points!, p.points))?.id ?? 'custom';\n}\n\n/** The profile of the reversed path: positions mirrored and the sides swapped. */\nexport function reverseWidthProfile(points: readonly VectorWidthPoint[]): VectorWidthPoint[] {\n\treturn points.map((p) => ({ t: 1 - p.t, left: p.right, right: p.left })).reverse();\n}\n\n/** The profile of a mirrored path (it runs the same way, but its sides swap). */\nexport function mirrorWidthProfile(points: readonly VectorWidthPoint[]): VectorWidthPoint[] {\n\treturn points.map((p) => ({ t: p.t, left: p.right, right: p.left }));\n}\n\n/** The points a profile edit starts from: the profile, or a uniform one with a point at each end. */\nfunction editable(points: readonly VectorWidthPoint[] | null | undefined): VectorWidthPoint[] {\n\treturn points?.length ? points.map((p) => ({ ...p })) : [sym(0, 1), sym(1, 1)];\n}\n\n/**\n * Adds a width point at `t` (side scales `left` / `right`, default: as wide as the stroke is\n * there) and returns the new profile and the point's index. Inserting at the position of an\n * existing point replaces it, unless `step` is set (which makes a discontinuous point).\n */\nexport function insertWidthPoint(\n\tpoints: readonly VectorWidthPoint[] | null | undefined,\n\tt: number,\n\tsides: { left?: number; right?: number } = {},\n\toptions: { step?: boolean } = {}\n): { points: VectorWidthPoint[]; index: number } {\n\tconst pts = editable(points);\n\tconst tt = Math.min(Math.max(Number.isFinite(t) ? t : 0, 0), 1);\n\tconst here = widthProfileAt(points?.length ? points : null, tt);\n\tconst p: VectorWidthPoint = {\n\t\tt: tt,\n\t\tleft: clean(sides.left, VECTOR_WIDTH_PROFILE_MAX_SCALE) ?? here.left,\n\t\tright: clean(sides.right, VECTOR_WIDTH_PROFILE_MAX_SCALE) ?? here.right\n\t};\n\tconst same = pts.findIndex((q) => Math.abs(q.t - tt) <= VECTOR_WIDTH_SAME_T);\n\tif (same >= 0 && !options.step) {\n\t\tpts[same] = { ...p, t: pts[same].t };\n\t\treturn { points: pts, index: same };\n\t}\n\tlet i = 0;\n\twhile (i < pts.length && pts[i].t <= tt) i++;\n\tif (pts.length >= VECTOR_WIDTH_PROFILE_MAX_POINTS) return { points: pts, index: Math.max(0, i - 1) };\n\tpts.splice(i, 0, p);\n\treturn { points: pts, index: i };\n}\n\n/**\n * Changes point `index`: moves it to `t` (kept between its neighbours, so indices stay put)\n * and / or sets its side scales. Out-of-range indices leave the profile as it is.\n */\nexport function setWidthPoint(points: readonly VectorWidthPoint[], index: number, patch: Partial<VectorWidthPoint>): VectorWidthPoint[] {\n\tconst pts = points.map((p) => ({ ...p }));\n\tconst p = pts[index];\n\tif (!p) return pts;\n\tif (typeof patch.t === 'number' && Number.isFinite(patch.t)) {\n\t\tconst lo = index > 0 ? pts[index - 1].t : 0;\n\t\tconst hi = index < pts.length - 1 ? pts[index + 1].t : 1;\n\t\tp.t = Math.min(Math.max(patch.t, lo), Math.max(hi, lo));\n\t}\n\tconst l = clean(patch.left, VECTOR_WIDTH_PROFILE_MAX_SCALE);\n\tconst r = clean(patch.right, VECTOR_WIDTH_PROFILE_MAX_SCALE);\n\tif (l !== null) p.left = l;\n\tif (r !== null) p.right = r;\n\treturn pts;\n}\n\n/** Removes the points at `indices`. */\nexport function removeWidthPoints(points: readonly VectorWidthPoint[], indices: readonly number[]): VectorWidthPoint[] {\n\tconst drop = new Set(indices);\n\treturn points.filter((_, i) => !drop.has(i)).map((p) => ({ ...p }));\n}\n\n// ---------------------------------------------------------------------------\n// Generators\n// ---------------------------------------------------------------------------\n\n/** effectcraft's ease from the tip (`u = 0`) to full width (`u = 1`): linear at 0, a quarter circle at 1. */\nfunction ease(u: number, e: number): number {\n\tconst x = Math.min(Math.max(u, 0), 1);\n\tconst round = Math.sqrt(Math.max(0, 1 - (1 - x) * (1 - x)));\n\treturn x + (round - x) * Math.min(Math.max(e, 0), 1);\n}\n\n/** A parametric taper (effectcraft's Stroke Taper), lengths as fractions of the path. */\nexport interface VectorTaperOptions {\n\t/** Fraction of the path the start taper covers (default 0). */\n\tstartLength?: number;\n\t/** Fraction the end taper covers (default 0). */\n\tendLength?: number;\n\t/** Width at the start as a fraction of the stroke width (default 0: a point). */\n\tstartWidth?: number;\n\tendWidth?: number;\n\t/** 0 linear, 1 a quarter circle bulging outwards (default 0). */\n\tstartEase?: number;\n\tendEase?: number;\n}\n\n/** A width wave (effectcraft's Stroke Wave): the width swings between `1 − amount` and 1. */\nexport interface VectorWaveOptions {\n\t/** 0–1 (default 0.5). */\n\tamount?: number;\n\t/** Whole cycles along the path (default 4). */\n\tcycles?: number;\n\t/** Degrees (default 0). */\n\tphase?: number;\n}\n\n/** The width factor at fraction `t` of a taper and / or a wave (effectcraft `width_factor`). */\nexport function taperWaveFactor(t: number, taper?: VectorTaperOptions | null, wave?: VectorWaveOptions | null): number {\n\tlet k = 1;\n\tif (taper) {\n\t\tlet ls = Math.max(0, taper.startLength ?? 0);\n\t\tlet le = Math.max(0, taper.endLength ?? 0);\n\t\t// Overlapping tapers share the path in proportion.\n\t\tif (ls + le > 1) {\n\t\t\tconst f = 1 / (ls + le);\n\t\t\tls *= f;\n\t\t\tle *= f;\n\t\t}\n\t\tconst sw = Math.min(Math.max(taper.startWidth ?? 0, 0), 1);\n\t\tconst ew = Math.min(Math.max(taper.endWidth ?? 0, 0), 1);\n\t\tif (ls > 0 && t < ls) k = Math.min(k, sw + (1 - sw) * ease(t / ls, taper.startEase ?? 0));\n\t\tif (le > 0 && t > 1 - le) k = Math.min(k, ew + (1 - ew) * ease((1 - t) / le, taper.endEase ?? 0));\n\t}\n\tif (wave) {\n\t\tconst cycles = wave.cycles ?? 4;\n\t\tconst amount = Math.min(Math.max(wave.amount ?? 0.5, 0), 1);\n\t\tif (cycles > 0 && amount > 0) {\n\t\t\tconst ph = 2 * Math.PI * t * cycles + ((wave.phase ?? 0) * Math.PI) / 180;\n\t\t\tk *= 1 - amount * (0.5 - 0.5 * Math.cos(ph));\n\t\t}\n\t}\n\treturn k;\n}\n\n/**\n * Samples a taper and / or wave into profile points (symmetric), simplified to within\n * `tolerance` of the factor (default 0.01). The monotone interpolation draws them back smoothly.\n */\nexport function generateWidthProfile(options: { taper?: VectorTaperOptions | null; wave?: VectorWaveOptions | null; samples?: number; tolerance?: number }): VectorWidthPoint[] {\n\tconst cycles = options.wave ? Math.max(0, options.wave.cycles ?? 4) : 0;\n\tconst n = Math.max(8, Math.min(VECTOR_WIDTH_PROFILE_MAX_POINTS * 4, options.samples ?? Math.ceil(32 + cycles * 16)));\n\tconst raw: [number, number][] = [];\n\tfor (let i = 0; i <= n; i++) raw.push([i / n, taperWaveFactor(i / n, options.taper, options.wave)]);\n\t// Keep the taper ends exactly.\n\tconst ts = [options.taper?.startLength, options.taper?.endLength !== undefined ? 1 - options.taper.endLength : undefined];\n\tfor (const t of ts) if (typeof t === 'number' && t > 0 && t < 1) raw.push([t, taperWaveFactor(t, options.taper, options.wave)]);\n\traw.sort((a, b) => a[0] - b[0]);\n\tconst kept = simplifySamples(raw, options.tolerance ?? 0.01, VECTOR_WIDTH_PROFILE_MAX_POINTS);\n\treturn kept.map(([t, k]) => sym(t, k));\n}\n\n/** Douglas–Peucker on (t, value) samples (vertical distance), then thinned to at most `max`. */\nfunction simplifySamples(samples: readonly [number, number][], tolerance: number, max: number): [number, number][] {\n\tif (samples.length <= 2) return samples.slice();\n\tconst keep = new Array(samples.length).fill(false);\n\tkeep[0] = keep[samples.length - 1] = true;\n\tconst stack: [number, number][] = [[0, samples.length - 1]];\n\twhile (stack.length) {\n\t\tconst [a, b] = stack.pop()!;\n\t\tconst [ta, va] = samples[a];\n\t\tconst [tb, vb] = samples[b];\n\t\tlet worst = -1;\n\t\tlet err = tolerance;\n\t\tfor (let i = a + 1; i < b; i++) {\n\t\t\tconst [t, v] = samples[i];\n\t\t\tconst lin = tb - ta > 1e-12 ? va + ((vb - va) * (t - ta)) / (tb - ta) : va;\n\t\t\tconst e = Math.abs(v - lin);\n\t\t\tif (e > err) {\n\t\t\t\terr = e;\n\t\t\t\tworst = i;\n\t\t\t}\n\t\t}\n\t\tif (worst >= 0) {\n\t\t\tkeep[worst] = true;\n\t\t\tstack.push([a, worst], [worst, b]);\n\t\t}\n\t}\n\tlet out = samples.filter((_, i) => keep[i]);\n\twhile (out.length > max) out = out.filter((_, i) => i === 0 || i === out.length - 1 || i % 2 === 0);\n\treturn out;\n}\n\n/** Pen pressure that draws the stroke's own width (a mouse reports 0.5 while a button is down). */\nexport const VECTOR_PRESSURE_NOMINAL = 0.5;\n\n/**\n * A profile from pen pressure recorded along a stroke: `(t, pressure 0–1)` samples in order.\n * The width scale is pressure ÷ 0.5 (so half pressure draws the stroke's width, full pressure\n * twice it), at least 0.05; the samples are simplified to within `tolerance` (default 0.02).\n * Returns an empty (uniform) profile when every sample is the same.\n */\nexport function pressureWidthProfile(samples: readonly { t: number; pressure: number }[], options: { tolerance?: number } = {}): VectorWidthPoint[] {\n\tconst raw: [number, number][] = samples\n\t\t.filter((s) => Number.isFinite(s.t) && Number.isFinite(s.pressure))\n\t\t.map((s) => [Math.min(Math.max(s.t, 0), 1), Math.max(0.05, Math.min(1, Math.max(0, s.pressure)) / VECTOR_PRESSURE_NOMINAL)] as [number, number]);\n\tif (raw.length < 2) return [];\n\tif (raw.every((s) => Math.abs(s[1] - raw[0][1]) < 1e-6)) return [];\n\treturn simplifySamples(raw, options.tolerance ?? 0.02, VECTOR_WIDTH_PROFILE_MAX_POINTS).map(([t, k]) => sym(t, Math.round(k * 1000) / 1000));\n}\n","/**\n * The outline of a variable-width stroke, as a clean filled Bézier path.\n *\n * Each subpath is flattened (finely enough for the width to vary smoothly) and the stroke is\n * the union of positively wound pieces: one trapezoid per flattened segment (its ends at the\n * local side widths), a wedge on the outer side of every bend (the style's join at the path's\n * own corners, round inside curves) and the caps. Filled with the non-zero rule the union is\n * exact whatever the profile does; it is built as the boundary those pieces add up to (see\n * `variableStrokeRings`), normalised with the polygon boolean engine, and every ring is refitted\n * with cubics (`fitCubics`) to within the tolerance — a real Bézier outline, not a polyline.\n *\n * Ported from ArtCraft effectcraft `crates/path/src/varstroke.rs` (`outline`, `dash_intervals`)\n * (MIT OR Apache-2.0, see THIRD_PARTY_NOTICES.md), with one-sided widths after figcraft\n * `crates/pathops/src/width.rs` (`variable_outline`).\n */\nimport type { Point } from '../canvas/types.js';\nimport { fitCubics, isStraightCubic } from '../canvas/curveFit.js';\nimport { polygonBoolean } from '../canvas/polygonBoolean.js';\nimport {\n\tflattenChain,\n\tpathToContours,\n\tsegmentLength,\n\tsegmentNearest,\n\tsegmentTangent,\n\ttype BezierPath,\n\ttype BezierSegment\n} from '../canvas/pathops/bezier.js';\nimport type { StrokeCap, StrokeJoin } from '../canvas/pathops/stroke.js';\nimport type { VectorWidthPoint } from './types.js';\nimport { widthProfileAt } from './width.js';\n\nexport interface VariableStrokeOptions {\n\t/** Stroke width: a side scale of 1 reaches half of it from the centre line. */\n\twidth: number;\n\tprofile: readonly VectorWidthPoint[];\n\t/** Joins at the path's own corners (default `miter`); bends inside curves are always round. */\n\tjoin?: StrokeJoin;\n\t/** Default `butt`. */\n\tcap?: StrokeCap;\n\t/** Default 4 (as SVG). */\n\tmiterLimit?: number;\n\t/** Dash pattern (as SVG); the profile still runs along the whole subpath. */\n\tdash?: readonly number[];\n\tdashOffset?: number;\n\t/** Largest distance of the result from the exact outline (default 0.05). */\n\ttolerance?: number;\n}\n\n/** Width samples per subpath at least (so the width varies smoothly along long straight runs). */\nconst SAMPLES_PER_SUBPATH = 128;\n/** The most dashes one subpath makes. */\nconst MAX_DASHES = 5000;\n\ntype Vec = [number, number];\nconst sub = (a: Point, b: Point): Vec => [a[0] - b[0], a[1] - b[1]];\nconst along = (p: Point, v: Vec, k: number): Point => [p[0] + v[0] * k, p[1] + v[1] * k];\nconst lerp = (a: Point, b: Point, u: number): Point => [a[0] + (b[0] - a[0]) * u, a[1] + (b[1] - a[1]) * u];\nconst dist = (a: Point, b: Point) => Math.hypot(a[0] - b[0], a[1] - b[1]);\nconst unitOf = (v: Vec): Vec => {\n\tconst l = Math.hypot(v[0], v[1]);\n\treturn l > 0 ? [v[0] / l, v[1] / l] : [1, 0];\n};\n/** The left normal of a direction on screen (y down): `(dy, −dx)`. */\nconst leftNormal = (d: Vec): Vec => [d[1], -d[0]];\n\n/** A flattened subpath: points, their arc lengths, and which are the path's own vertices. */\ninterface Poly {\n\tpts: Point[];\n\ts: number[];\n\tcorner: boolean[];\n\tclosed: boolean;\n}\n\nfunction flatten(segments: readonly BezierSegment[], closed: boolean, tol: number): Poly | null {\n\tconst pts: Point[] = [];\n\tconst corner: boolean[] = [];\n\tconst push = (p: Point, isCorner: boolean) => {\n\t\tconst last = pts[pts.length - 1];\n\t\tif (last && dist(last, p) <= 1e-9) {\n\t\t\tcorner[corner.length - 1] ||= isCorner;\n\t\t\treturn;\n\t\t}\n\t\tpts.push(p);\n\t\tcorner.push(isCorner);\n\t};\n\tfor (const seg of segments) {\n\t\tconst flat = flattenChain([seg], tol);\n\t\t// flattenChain drops a closing duplicate; segments that end where they start keep it.\n\t\tif (dist(seg[0], seg[seg.length - 1]) <= 1e-12 && flat.length > 1) flat.push(seg[seg.length - 1]);\n\t\tflat.forEach((p, i) => push(p, i === 0 || i === flat.length - 1));\n\t}\n\tif (closed && pts.length > 2 && dist(pts[0], pts[pts.length - 1]) > 1e-9) push(pts[0], true);\n\tif (pts.length < 2) return null;\n\t// Subdivide so no piece is longer than the width sampling step.\n\tlet total = 0;\n\tfor (let i = 1; i < pts.length; i++) total += dist(pts[i - 1], pts[i]);\n\tif (!(total > 0)) return null;\n\tconst step = Math.max(total / SAMPLES_PER_SUBPATH, tol);\n\tconst fine: Point[] = [pts[0]];\n\tconst fineCorner: boolean[] = [corner[0]];\n\tfor (let i = 1; i < pts.length; i++) {\n\t\tconst n = Math.max(1, Math.ceil(dist(pts[i - 1], pts[i]) / step));\n\t\tfor (let k = 1; k <= n; k++) {\n\t\t\tfine.push(k === n ? pts[i] : lerp(pts[i - 1], pts[i], k / n));\n\t\t\tfineCorner.push(k === n ? corner[i] : false);\n\t\t}\n\t}\n\tconst s = [0];\n\tfor (let i = 1; i < fine.length; i++) s.push(s[i - 1] + dist(fine[i - 1], fine[i]));\n\treturn { pts: fine, s, corner: fineCorner, closed };\n}\n\n/** Arc-length intervals that are \"on\" for a dash pattern on a path `len` long (effectcraft `dash_intervals`). */\nexport function dashIntervals(pattern: readonly number[], offset: number, len: number): [number, number][] {\n\tlet pat = pattern.map((v) => (Number.isFinite(v) ? Math.max(0, v) : 0));\n\tif (pat.length % 2 === 1) pat = [...pat, ...pat];\n\tconst period = pat.reduce((a, b) => a + b, 0);\n\tif (pat.length < 2 || !(period > 1e-9) || !(len > 0)) return [[0, len]];\n\tlet pos = (((Number.isFinite(offset) ? offset : 0) % period) + period) % period;\n\tlet idx = 0;\n\twhile (pos >= pat[idx]) {\n\t\tpos -= pat[idx];\n\t\tidx = (idx + 1) % pat.length;\n\t}\n\tconst out: [number, number][] = [];\n\tlet s = 0;\n\tlet guard = 0;\n\twhile (s < len && guard++ < MAX_DASHES * pat.length) {\n\t\tconst e = Math.min(s + pat[idx] - pos, len);\n\t\tif (idx % 2 === 0 && e >= s) out.push([s, e]);\n\t\ts += pat[idx] - pos;\n\t\tpos = 0;\n\t\tidx = (idx + 1) % pat.length;\n\t}\n\treturn out;\n}\n\n/** The point at arc length `t` of a polyline and the index of the segment it lies on. */\nfunction at(poly: Poly, t: number): [Point, number] {\n\tlet i = 1;\n\twhile (i < poly.pts.length - 1 && poly.s[i] <= t) i++;\n\tconst a = poly.s[i - 1];\n\tconst b = poly.s[i];\n\tconst u = b > a ? Math.min(Math.max((t - a) / (b - a), 0), 1) : 0;\n\treturn [lerp(poly.pts[i - 1], poly.pts[i], u), i];\n}\n\n/** Points of an arc around `c` from angle `a0` turning by `sweep`, radius going from `r0` to `r1` (ends excluded). */\nfunction arcPoints(c: Point, a0: number, sweep: number, r0: number, r1: number, tol: number): Point[] {\n\tconst r = Math.max(r0, r1);\n\tif (!(r > tol * 0.5) || !Number.isFinite(sweep)) return [];\n\tconst stepAngle = 2 * Math.acos(Math.min(Math.max(1 - tol / r, -1), 1));\n\tconst n = Math.min(128, Math.max(2, Math.ceil(Math.abs(sweep) / Math.max(stepAngle, 1e-3))));\n\tconst out: Point[] = [];\n\tfor (let k = 1; k < n; k++) {\n\t\tconst f = k / n;\n\t\tconst a = a0 + sweep * f;\n\t\tconst rr = r0 + (r1 - r0) * f;\n\t\tout.push([c[0] + Math.cos(a) * rr, c[1] + Math.sin(a) * rr]);\n\t}\n\treturn out;\n}\n\n/** Where segments `a0 → a1` and `b0 → b1` cross, if they do (strictly inside `b`, anywhere past the start of `a`). */\nfunction crossing(a0: Point, a1: Point, b0: Point, b1: Point): Point | null {\n\tconst r = sub(a1, a0);\n\tconst q = sub(b1, b0);\n\tconst den = r[0] * q[1] - r[1] * q[0];\n\tif (Math.abs(den) < 1e-12) return null;\n\tconst w = sub(b0, a0);\n\tconst t = (w[0] * q[1] - w[1] * q[0]) / den;\n\tconst u = (w[0] * r[1] - w[1] * r[0]) / den;\n\treturn t > 1e-9 && t <= 1 && u >= 0 && u < 1 - 1e-9 ? [a0[0] + r[0] * t, a0[1] + r[1] * t] : null;\n}\n\n/**\n * The rings of a variable-width stroke in the path's own space; filled with the non-zero rule\n * they are the stroke.\n *\n * effectcraft builds the stroke as a union of positively wound pieces: a trapezoid per flattened\n * segment (its ends at the local side widths), a wedge on the outer side of every bend and the\n * caps. Unioning thousands of overlapping pieces is slow, so each run is emitted as the boundary\n * those pieces add up to instead (winding numbers add, so the non-zero fill is the same): the\n * offset sides with the outer join between them, and on the inner side of a bend either the point\n * where the two offsets cross (the region cut off is covered by both trapezoids) or, when they do\n * not cross, a detour through the centre point (which keeps the winding right when segments are\n * shorter than the width). A closed subpath gives two rings, an open run one.\n */\nexport function variableStrokeRings(path: BezierPath, options: VariableStrokeOptions): Point[][] {\n\tconst half = options.width / 2;\n\tif (!(half > 0) || !Number.isFinite(half)) return [];\n\tconst tol = options.tolerance && options.tolerance > 0 ? options.tolerance : 0.05;\n\tconst join = options.join ?? 'miter';\n\tconst cap = options.cap ?? 'butt';\n\tconst miterLimit = options.miterLimit && options.miterLimit >= 1 ? options.miterLimit : 4;\n\tconst rings: Point[][] = [];\n\tconst push = (out: Point[], p: Point) => {\n\t\tconst last = out[out.length - 1];\n\t\tif (!last || dist(last, p) > 1e-12) out.push(p);\n\t};\n\tfor (const contour of pathToContours(path)) {\n\t\tif (!contour.segments.length) continue;\n\t\t// Chords of the offsets lose area on the outer side of a curve; a quarter of the tolerance keeps that well inside it.\n\t\tconst poly = flatten(contour.segments, contour.closed, Math.min(tol, half) / 4);\n\t\tif (!poly) continue;\n\t\tconst len = poly.s[poly.s.length - 1];\n\t\tconst sides = (s: number): [number, number] => {\n\t\t\tconst w = widthProfileAt(options.profile, s / len);\n\t\t\treturn [w.left * half, w.right * half];\n\t\t};\n\t\tconst intervals = options.dash?.some((v) => v > 0) ? dashIntervals(options.dash, options.dashOffset ?? 0, len) : [[0, len] as [number, number]];\n\t\tconst whole = intervals.length === 1 && intervals[0][0] <= 0 && intervals[0][1] >= len;\n\t\tfor (const [a, b] of intervals) {\n\t\t\tconst [pa, ia] = at(poly, a);\n\t\t\tconst [pb, ib] = at(poly, b);\n\t\t\tconst pts: { p: Point; s: number; corner: boolean }[] = [{ p: pa, s: a, corner: poly.corner[0] && a <= 0 }];\n\t\t\tfor (let i = ia; i < ib; i++) pts.push({ p: poly.pts[i], s: poly.s[i], corner: poly.corner[i] });\n\t\t\tpts.push({ p: pb, s: b, corner: false });\n\t\t\tconst run = pts.filter((q, i) => i === 0 || dist(q.p, pts[i - 1].p) > 1e-9);\n\t\t\tif (run.length < 2) {\n\t\t\t\t// A zero-length dash: only its caps show.\n\t\t\t\tconst [l, r] = sides(a);\n\t\t\t\tconst w = (l + r) / 2;\n\t\t\t\tif (cap === 'round' && w > 0) rings.push([[pa[0] + w, pa[1]], ...arcPoints(pa, 0, 2 * Math.PI, w, w, tol)]);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst closed = whole && poly.closed;\n\t\t\tconst m = run.length - 1;\n\t\t\tconst dirs: Vec[] = [];\n\t\t\tfor (let i = 0; i < m; i++) dirs.push(unitOf(sub(run[i + 1].p, run[i].p)));\n\t\t\tconst normals = dirs.map(leftNormal);\n\t\t\tconst w = run.map((q) => sides(q.s));\n\n\t\t\t/** One side (`sigma` +1 left, −1 right) along the run, joins included. */\n\t\t\tconst side = (sigma: 1 | -1): Point[] => {\n\t\t\t\tconst k = sigma > 0 ? 0 : 1;\n\t\t\t\tconst S = (i: number) => along(run[i].p, normals[i], sigma * w[i][k]);\n\t\t\t\tconst E = (i: number) => along(run[i + 1].p, normals[i], sigma * w[i + 1][k]);\n\t\t\t\tconst out: Point[] = [S(0)];\n\t\t\t\tfor (let i = 0; i < m; i++) {\n\t\t\t\t\tconst seam = i === m - 1;\n\t\t\t\t\tif (seam && !closed) {\n\t\t\t\t\t\tpush(out, E(i));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tconst j = seam ? 0 : i + 1;\n\t\t\t\t\tconst p = run[i + 1].p;\n\t\t\t\t\tconst d0 = dirs[i];\n\t\t\t\t\tconst d1 = dirs[j];\n\t\t\t\t\tconst cross = d0[0] * d1[1] - d0[1] * d1[0];\n\t\t\t\t\tconst dot = d0[0] * d1[0] + d0[1] * d1[1];\n\t\t\t\t\tconst r0 = w[i + 1][k];\n\t\t\t\t\tconst r1 = w[j][k];\n\t\t\t\t\tconst e = E(i);\n\t\t\t\t\tconst st = S(j);\n\t\t\t\t\tif (Math.abs(cross) < 1e-12 && dot > 0) {\n\t\t\t\t\t\tpush(out, e);\n\t\t\t\t\t\tif (!seam) push(out, st);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tconst uturn = Math.abs(cross) < 1e-12;\n\t\t\t\t\t// Turning right on screen puts the left side outside the bend (a U-turn: the left side).\n\t\t\t\t\tconst outer = uturn ? sigma > 0 : cross * sigma > 0;\n\t\t\t\t\tif (!outer) {\n\t\t\t\t\t\tconst x = seam ? null : crossing(out[out.length - 1], e, st, E(j));\n\t\t\t\t\t\tif (x) push(out, x);\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tpush(out, e);\n\t\t\t\t\t\t\tpush(out, p);\n\t\t\t\t\t\t\tif (!seam) push(out, st);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tpush(out, e);\n\t\t\t\t\tconst theta = uturn ? Math.PI : Math.atan2(cross, dot);\n\t\t\t\t\tconst r = (r0 + r1) / 2;\n\t\t\t\t\tconst kind: StrokeJoin = run[i + 1].corner || (seam && poly.corner[0]) ? join : 'round';\n\t\t\t\t\tif (r > 1e-9) {\n\t\t\t\t\t\tif (kind === 'round') {\n\t\t\t\t\t\t\tif (r * (1 - Math.cos(Math.abs(theta) / 2)) > tol * 0.25) {\n\t\t\t\t\t\t\t\tfor (const q of arcPoints(p, Math.atan2(e[1] - p[1], e[0] - p[0]), theta, r0, r1, tol)) push(out, q);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (kind === 'miter') {\n\t\t\t\t\t\t\tconst halfCos = Math.cos(theta / 2);\n\t\t\t\t\t\t\tconst bis: Vec = [normals[i][0] + normals[j][0], normals[i][1] + normals[j][1]];\n\t\t\t\t\t\t\tconst bl = Math.hypot(bis[0], bis[1]);\n\t\t\t\t\t\t\tif (halfCos > 1e-6 && 1 / halfCos <= miterLimit && bl > 1e-9) push(out, along(p, bis, (sigma * r) / halfCos / bl));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!seam) push(out, st);\n\t\t\t\t}\n\t\t\t\tif (closed && out.length > 1 && dist(out[0], out[out.length - 1]) <= 1e-12) out.pop();\n\t\t\t\treturn out;\n\t\t\t};\n\t\t\tconst left = side(1);\n\t\t\tconst right = side(-1);\n\t\t\tif (closed) {\n\t\t\t\trings.push(left, right.reverse());\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Caps: round or square around each end, from one side to the other.\n\t\t\tconst capPoints = (atStart: boolean): Point[] => {\n\t\t\t\tif (cap === 'butt') return [];\n\t\t\t\tconst i = atStart ? 0 : m;\n\t\t\t\tconst d = atStart ? dirs[0] : dirs[m - 1];\n\t\t\t\tconst [l, r] = w[i];\n\t\t\t\tif (!(l + r > 1e-9)) return [];\n\t\t\t\tconst p = run[i].p;\n\t\t\t\tconst n = leftNormal(d);\n\t\t\t\tconst L = along(p, n, l);\n\t\t\t\tconst R = along(p, n, -r);\n\t\t\t\tif (cap === 'round') {\n\t\t\t\t\t// Half a turn around the outside: right side to left at the start, left to right at the end.\n\t\t\t\t\tconst a0 = Math.atan2(d[1], d[0]) + (atStart ? Math.PI / 2 : -Math.PI / 2);\n\t\t\t\t\treturn arcPoints(p, a0, Math.PI, atStart ? r : l, atStart ? l : r, tol);\n\t\t\t\t}\n\t\t\t\tconst ext = (l + r) / 2;\n\t\t\t\tconst o: Vec = atStart ? [-d[0], -d[1]] : d;\n\t\t\t\treturn atStart ? [along(R, o, ext), along(L, o, ext)] : [along(L, o, ext), along(R, o, ext)];\n\t\t\t};\n\t\t\tconst ring: Point[] = [];\n\t\t\tfor (const q of [...left, ...capPoints(false), ...right.reverse(), ...capPoints(true)]) push(ring, q);\n\t\t\tif (ring.length > 2) rings.push(ring);\n\t\t}\n\t}\n\treturn rings;\n}\n\n/**\n * The outline of a variable-width stroke of `path` (side widths from `profile`), as a clean\n * Bézier path: overlaps removed, outer rings and holes, each refitted with cubics to within\n * `tolerance` of the exact outline. Fill it with the non-zero rule (either rule works).\n */\nexport function variableStrokeOutline(path: BezierPath, options: VariableStrokeOptions): BezierPath {\n\tconst pieces = variableStrokeRings(path, options);\n\tif (!pieces.length) return [];\n\tconst tol = options.tolerance && options.tolerance > 0 ? options.tolerance : 0.05;\n\tlet polygons;\n\ttry {\n\t\tpolygons = polygonBoolean('union', [{ rings: pieces, fillRule: 'nonzero' }]);\n\t} catch {\n\t\treturn [];\n\t}\n\tconst out: BezierPath = [];\n\tfor (const polygon of polygons) {\n\t\tfor (const ring of polygon) {\n\t\t\tif (ring.length < 3) continue;\n\t\t\tconst cubics = fitCubics(ring, tol, { closed: true, cornerAngleDeg: 25 });\n\t\t\tif (!cubics.length) continue;\n\t\t\tout.push({ type: 'M', x: cubics[0].p0[0], y: cubics[0].p0[1] });\n\t\t\tcubics.forEach((c, i) => {\n\t\t\t\tif (isStraightCubic(c, tol * 0.02)) {\n\t\t\t\t\tif (i < cubics.length - 1) out.push({ type: 'L', x: c.p3[0], y: c.p3[1] });\n\t\t\t\t} else out.push({ type: 'C', x1: c.p1[0], y1: c.p1[1], x2: c.p2[0], y2: c.p2[1], x: c.p3[0], y: c.p3[1] });\n\t\t\t});\n\t\t\tout.push({ type: 'Z' });\n\t\t}\n\t}\n\treturn out;\n}\n\n/**\n * Where point `p` lies relative to the stroke's centre line: the nearest point's contour,\n * its position `t` by length (0–1), the distance, and the side (`left` looking along the\n * path on screen) — what hit-testing and the width tool need.\n */\nexport function nearestStrokePosition(\n\tpath: BezierPath,\n\tp: Point,\n\t/** Only this contour (subpath) of the path. */\n\tonly?: number\n): { contour: number; t: number; distance: number; side: 'left' | 'right'; point: Point; tangent: Vec; length: number } | null {\n\tlet best: ReturnType<typeof nearestStrokePosition> = null;\n\tpathToContours(path).forEach((c, ci) => {\n\t\tif (!c.segments.length || (only !== undefined && ci !== only)) return;\n\t\tconst lengths = c.segments.map((s) => segmentLength(s));\n\t\tconst total = lengths.reduce((a, b) => a + b, 0);\n\t\tif (!(total > 0)) return;\n\t\tlet acc = 0;\n\t\tc.segments.forEach((seg, si) => {\n\t\t\tconst near = segmentNearest(seg, p);\n\t\t\tif (!best || near.distance < best.distance) {\n\t\t\t\tconst s = acc + segmentLength(seg, 0, near.t);\n\t\t\t\tconst tan = segmentTangent(seg, near.t);\n\t\t\t\tconst pt = seg.length === 2 ? lerp(seg[0], seg[1], near.t) : evalSeg(seg, near.t);\n\t\t\t\tconst n = leftNormal([tan[0], tan[1]]);\n\t\t\t\tconst side = (p[0] - pt[0]) * n[0] + (p[1] - pt[1]) * n[1] >= 0 ? 'left' : 'right';\n\t\t\t\tbest = { contour: ci, t: Math.min(Math.max(s / total, 0), 1), distance: near.distance, side, point: pt, tangent: [tan[0], tan[1]], length: total };\n\t\t\t}\n\t\t\tacc += lengths[si];\n\t\t});\n\t});\n\treturn best;\n}\n\nfunction evalSeg(seg: BezierSegment, t: number): Point {\n\t// de Casteljau\n\tlet pts = seg.map((q) => [q[0], q[1]] as Point);\n\twhile (pts.length > 1) pts = pts.slice(1).map((q, i) => lerp(pts[i], q, t));\n\treturn pts[0];\n}\n\n/** The point and unit tangent at position `t` (0–1 by length) of contour `index` of `path`. */\nexport function strokePositionPoint(path: BezierPath, index: number, t: number): { point: Point; tangent: Vec } | null {\n\tconst c = pathToContours(path)[index];\n\tif (!c?.segments.length) return null;\n\tconst lengths = c.segments.map((s) => segmentLength(s));\n\tconst total = lengths.reduce((a, b) => a + b, 0);\n\tlet want = Math.min(Math.max(t, 0), 1) * total;\n\tfor (let i = 0; i < c.segments.length; i++) {\n\t\tconst seg = c.segments[i];\n\t\tif (want <= lengths[i] + 1e-12 || i === c.segments.length - 1) {\n\t\t\t// Bisect the parameter for the remaining length.\n\t\t\tlet lo = 0;\n\t\t\tlet hi = 1;\n\t\t\tconst target = Math.min(want, lengths[i]);\n\t\t\tfor (let k = 0; k < 40; k++) {\n\t\t\t\tconst mid = (lo + hi) / 2;\n\t\t\t\tif (segmentLength(seg, 0, mid) < target) lo = mid;\n\t\t\t\telse hi = mid;\n\t\t\t}\n\t\t\tconst u = (lo + hi) / 2;\n\t\t\tconst tan = segmentTangent(seg, u);\n\t\t\treturn { point: evalSeg(seg, u), tangent: [tan[0], tan[1]] };\n\t\t}\n\t\twant -= lengths[i];\n\t}\n\treturn null;\n}\n","/**\n * Stroke options of vector paths: alignment (centre, inside, outside) and arrowheads at the\n * ends of open paths. Everything here works on a path's Bézier outline in one space (the\n * node's own, or the document's), so the canvases, hit-testing, SVG export and Outline Stroke\n * share it.\n *\n * Ported from ArtCraft (MIT OR Apache-2.0, see THIRD_PARTY_NOTICES.md):\n * - figcraft `crates/pathops/src/lib.rs` — `stroke_outline_of`: an inside stroke is a stroke of\n * twice the width intersected with the fill, an outside one twice the width minus the fill,\n * and only closed shapes have an inside (open paths stroke centred).\n * - vectorcraft `crates/effects/src/stroke/arrow.rs` — arrowhead shapes, sizes and insets (where\n * the stroke stops under each head), `tip` vs `extend` alignment and trimming the stroke by\n * arc length under the head.\n */\nimport type { Point } from '../canvas/types.js';\nimport {\n\tcontoursToPath,\n\tdistanceToBezierPath,\n\tpathToContours,\n\tpointInBezierPath,\n\tsegmentLength,\n\tsegmentTangent,\n\ttrimSegments,\n\ttype BezierContour,\n\ttype BezierPath,\n\ttype BezierSegment\n} from '../canvas/pathops/bezier.js';\nimport { pathBoolean, type PathOperandInput } from '../canvas/pathops/boolean.js';\nimport { outlineStroke } from '../canvas/pathops/stroke.js';\nimport type { VectorArrowhead, VectorStrokeAlign, VectorStyle, VectorWidthPoint, Vec2 } from './types.js';\nimport { nearestStrokePosition, variableStrokeOutline } from './varstroke.js';\nimport { isUniformWidthProfile, sanitizeWidthProfile, widthProfileAt, widthProfileMaxScale } from './width.js';\n\n/** Handle length that makes a cubic approximate a quarter circle. */\nconst KAPPA = 0.5522847498307936;\n/** Where the notch of the (notched) arrow sits, as a fraction of the head's length. */\nconst NOTCH = 0.7;\n\n/** One arrowhead: a filled outline (nonzero) painted with the stroke's paint. */\nexport interface VectorArrowheadShape {\n\tend: 'start' | 'end';\n\tkind: VectorArrowhead;\n\ttip: Vec2;\n\t/** Unit direction the head points in. */\n\tdir: Vec2;\n\t/** Distance from the tip back to where the stroke ends under the head. */\n\tinset: number;\n\tpath: BezierPath;\n}\n\n/** What the stroke of a path draws. */\nexport interface VectorStrokeParts {\n\t/** Where the stroke sits (`center` for open shapes, whatever the style says). */\n\talign: VectorStrokeAlign;\n\t/** The line the stroke follows: the outline, shortened under `tip`-aligned heads. */\n\tline: BezierPath;\n\t/** The stroke width to draw `line` with: twice the style's width for inside / outside (half is clipped away). */\n\tdrawWidth: number;\n\theads: VectorArrowheadShape[];\n}\n\n/** True when the path has subpaths and all of them are closed (only such shapes have an inside). */\nexport function isClosedBezierShape(path: BezierPath): boolean {\n\tconst contours = pathToContours(path).filter((c) => c.segments.length > 0);\n\treturn contours.length > 0 && contours.every((c) => c.closed);\n}\n\n/** The alignment a stroke is drawn with: the style's, or `center` for open shapes. */\nexport function effectiveStrokeAlign(style: Pick<VectorStyle, 'strokeAlign'>, closedShape: boolean): VectorStrokeAlign {\n\tconst a = style.strokeAlign ?? 'center';\n\treturn closedShape ? a : 'center';\n}\n\n/** The style's width profile, sanitised, or null when the stroke is uniform. */\nexport function strokeWidthProfileOf(style: Pick<VectorStyle, 'strokeWidthProfile'>): VectorWidthPoint[] | null {\n\tif (!style.strokeWidthProfile?.length) return null;\n\tconst p = sanitizeWidthProfile(style.strokeWidthProfile);\n\treturn isUniformWidthProfile(p) ? null : p;\n}\n\n/** True when the style asks for more than a plain centred stroke (an alignment, arrowheads or a width profile). */\nexport function hasStrokeExtras(style: VectorStyle): boolean {\n\treturn (style.strokeAlign ?? 'center') !== 'center' || !!style.startArrow || !!style.endArrow || !!strokeWidthProfileOf(style);\n}\n\n/**\n * The area of the stroke along `parts.line` before alignment clipping: SVG's stroke for a\n * uniform width, the variable-width outline (`variableStrokeOutline`) for a profiled one.\n */\nexport function vectorStrokeLineArea(parts: Pick<VectorStrokeParts, 'line' | 'drawWidth'>, style: VectorStyle, tolerance?: number): BezierPath {\n\tconst profile = strokeWidthProfileOf(style);\n\tconst dash = style.strokeDasharray?.length ? { dash: style.strokeDasharray } : {};\n\tconst join = style.strokeLinejoin ?? 'miter';\n\tconst cap = style.strokeLinecap ?? 'butt';\n\tif (profile) return variableStrokeOutline(parts.line, { width: parts.drawWidth, profile, join, cap, ...dash, ...(tolerance ? { tolerance } : {}) });\n\treturn outlineStroke(parts.line, { width: parts.drawWidth, join, cap, ...dash, ...(tolerance ? { tolerance } : {}) });\n}\n\nconst capExtent = (style: VectorStyle, width: number) => (style.strokeLinecap === 'round' || style.strokeLinecap === 'square' ? width / 2 : 0);\n\n/**\n * The weight of a head: the stroke's width just before it times the head's scale (at least\n * 0.25). A head is 4 weights long and wide. On a variable-width stroke the width there is the\n * profile's (the mean of its two sides at that end), floored at the stroke width, so a stroke\n * that tapers to nothing still gets a full-size head and one that swells gets a bigger one.\n */\nexport function arrowheadWeight(style: VectorStyle, end: 'start' | 'end'): number {\n\tconst scale = end === 'start' ? style.startArrowScale : style.endArrowScale;\n\tconst s = typeof scale === 'number' && scale > 0 && Number.isFinite(scale) ? scale : 1;\n\tconst width = style.strokeWidth ?? 1;\n\tconst profile = strokeWidthProfileOf(style);\n\tconst at = profile ? widthProfileAt(profile, end === 'start' ? 0 : 1) : null;\n\tconst here = at ? Math.max(width, (width * (at.left + at.right)) / 2) : width;\n\treturn Math.max(0.25, here * s);\n}\n\n/** Distance from the tip where the stroke ends, for a stroke whose cap reaches `cap` past its end. */\nexport function arrowheadInset(kind: VectorArrowhead, hw: number, cap: number): number {\n\tconst l = 4 * hw;\n\tconst solid = (back: number) => Math.max(back / 2, cap);\n\tswitch (kind) {\n\t\tcase 'triangle':\n\t\tcase 'circle':\n\t\tcase 'square':\n\t\t\treturn solid(l);\n\t\tcase 'arrow':\n\t\t\treturn solid(NOTCH * l);\n\t\tcase 'bar':\n\t\t\treturn solid(0.75 * hw);\n\t\tcase 'open':\n\t\t\t// The inner corner of the chevron: arm / sin(half angle).\n\t\t\treturn Math.max((hw * Math.sqrt(l * l + (l * l) / 4)) / (l / 2), cap);\n\t}\n}\n\nconst v = (x: number, y: number): Vec2 => ({ x, y });\nconst plus = (a: Vec2, b: Vec2, k = 1): Vec2 => ({ x: a.x + b.x * k, y: a.y + b.y * k });\nconst unit = (a: Vec2): Vec2 => {\n\tconst l = Math.hypot(a.x, a.y);\n\treturn l > 0 ? { x: a.x / l, y: a.y / l } : { x: 1, y: 0 };\n};\n\nfunction polygon(pts: Vec2[]): BezierPath {\n\treturn [{ type: 'M', x: pts[0].x, y: pts[0].y }, ...pts.slice(1).map((p) => ({ type: 'L' as const, x: p.x, y: p.y })), { type: 'Z' }];\n}\n\nfunction circle(c: Vec2, r: number): BezierPath {\n\tconst k = r * KAPPA;\n\tconst { x, y } = c;\n\treturn [\n\t\t{ type: 'M', x: x + r, y },\n\t\t{ type: 'C', x1: x + r, y1: y + k, x2: x + k, y2: y + r, x, y: y + r },\n\t\t{ type: 'C', x1: x - k, y1: y + r, x2: x - r, y2: y + k, x: x - r, y },\n\t\t{ type: 'C', x1: x - r, y1: y - k, x2: x - k, y2: y - r, x, y: y - r },\n\t\t{ type: 'C', x1: x + k, y1: y - r, x2: x + r, y2: y - k, x: x + r, y },\n\t\t{ type: 'Z' }\n\t];\n}\n\n/** The outline of a `kind` head of weight `hw` with its tip at `tip`, pointing along unit `dir`. */\nexport function arrowheadPath(kind: VectorArrowhead, tip: Vec2, dir: Vec2, hw: number): BezierPath {\n\tconst n = v(-dir.y, dir.x);\n\tconst l = 4 * hw;\n\tconst half = l / 2;\n\tconst base = plus(tip, dir, -l);\n\tswitch (kind) {\n\t\tcase 'triangle':\n\t\t\treturn polygon([tip, plus(base, n, half), plus(base, n, -half)]);\n\t\tcase 'arrow':\n\t\t\treturn polygon([tip, plus(base, n, half), plus(tip, dir, -NOTCH * l), plus(base, n, -half)]);\n\t\tcase 'open': {\n\t\t\t// Arms `hw` thick from the tip to the base corners, cut square at their ends.\n\t\t\tconst t = hw;\n\t\t\tconst a = plus(base, n, half);\n\t\t\tconst b = plus(base, n, -half);\n\t\t\tconst ua = unit(plus(a, tip, -1));\n\t\t\tconst ub = unit(plus(b, tip, -1));\n\t\t\tconst na = v(-ua.y, ua.x);\n\t\t\tconst nb = v(ub.y, -ub.x);\n\t\t\tconst inner = plus(tip, dir, -(t * Math.hypot(a.x - tip.x, a.y - tip.y)) / half);\n\t\t\treturn polygon([tip, a, plus(a, na, t), inner, plus(b, nb, t), b]);\n\t\t}\n\t\tcase 'circle':\n\t\t\treturn circle(plus(tip, dir, -half), half);\n\t\tcase 'square':\n\t\t\treturn polygon([plus(tip, n, half), plus(base, n, half), plus(base, n, -half), plus(tip, n, -half)]);\n\t\tcase 'bar': {\n\t\t\tconst t = 0.75 * hw;\n\t\t\treturn polygon([plus(tip, n, half), plus(plus(tip, n, half), dir, -t), plus(plus(tip, n, -half), dir, -t), plus(tip, n, -half)]);\n\t\t}\n\t}\n}\n\nconst toVec = (p: Point): Vec2 => ({ x: p[0], y: p[1] });\nconst segLen = (s: BezierSegment) => segmentLength(s);\n\n/** The end point of a contour and the unit direction pointing out of it there. */\nfunction endOf(segs: readonly BezierSegment[], atStart: boolean): { p: Vec2; out: Vec2 } {\n\tconst long = (s: BezierSegment) => segLen(s) > 1e-9;\n\tif (atStart) {\n\t\tconst s = segs.find(long) ?? segs[0];\n\t\tconst t = segmentTangent(s, 0);\n\t\treturn { p: toVec(segs[0][0]), out: v(-t[0], -t[1]) };\n\t}\n\tconst s = [...segs].reverse().find(long) ?? segs[segs.length - 1];\n\tconst t = segmentTangent(s, 1);\n\tconst last = segs[segs.length - 1];\n\treturn { p: toVec(last[last.length - 1]), out: v(t[0], t[1]) };\n}\n\n/** The point `dist` along the contour from its start (or back from its end), if it is that long. */\nfunction pointAlong(segs: readonly BezierSegment[], fromStart: boolean, dist: number): Vec2 | null {\n\tconst order = fromStart ? segs : [...segs].reverse();\n\tlet acc = 0;\n\tfor (const s of order) {\n\t\tconst l = segLen(s);\n\t\tif (acc + l >= dist) {\n\t\t\tconst want = fromStart ? dist - acc : l - (dist - acc);\n\t\t\tconst pts = trimSegments([s], 0, want);\n\t\t\tconst p = pts.length ? pts[0][pts[0].length - 1] : s[0];\n\t\t\treturn toVec(p);\n\t\t}\n\t\tacc += l;\n\t}\n\treturn null;\n}\n\n/**\n * The stroke of `path` as `style` draws it: its alignment, the line it follows (shortened under\n * `tip`-aligned heads) and its arrowheads. Heads go on the start of the first subpath and the\n * end of the last one, when those are open.\n */\nexport function vectorStrokeParts(path: BezierPath, style: VectorStyle): VectorStrokeParts {\n\tconst width = style.strokeWidth ?? 1;\n\tconst contours = pathToContours(path).filter((c) => c.segments.length > 0);\n\tconst closedShape = contours.length > 0 && contours.every((c) => c.closed);\n\tconst align = effectiveStrokeAlign(style, closedShape);\n\tconst drawWidth = align === 'center' ? width : width * 2;\n\tconst heads: VectorArrowheadShape[] = [];\n\tif (!contours.length || (!style.startArrow && !style.endArrow)) return { align, line: path, drawWidth, heads };\n\tconst first = contours[0];\n\tconst last = contours[contours.length - 1];\n\tconst cap = capExtent(style, width);\n\tconst tipMode = style.arrowAlign === 'tip';\n\tconst trims = contours.map(() => [0, 0]);\n\tconst place = (end: 'start' | 'end', contour: BezierContour, ci: number) => {\n\t\tconst kind = end === 'start' ? style.startArrow : style.endArrow;\n\t\tif (!kind || contour.closed) return;\n\t\tconst hw = arrowheadWeight(style, end);\n\t\tconst inset = arrowheadInset(kind, hw, cap);\n\t\tconst atStart = end === 'start';\n\t\tconst { p, out } = endOf(contour.segments, atStart);\n\t\tlet tip: Vec2;\n\t\tlet dir: Vec2;\n\t\tif (tipMode) {\n\t\t\t// Point along the chord from where the stroke now ends to the end point.\n\t\t\tconst q = pointAlong(contour.segments, atStart, inset);\n\t\t\tconst d = q ? v(p.x - q.x, p.y - q.y) : null;\n\t\t\tdir = d && Math.hypot(d.x, d.y) > 1e-9 ? unit(d) : out;\n\t\t\ttip = p;\n\t\t\ttrims[ci][atStart ? 0 : 1] = inset;\n\t\t} else {\n\t\t\ttip = plus(p, out, inset);\n\t\t\tdir = out;\n\t\t}\n\t\theads.push({ end, kind, tip, dir, inset, path: arrowheadPath(kind, tip, dir, hw) });\n\t};\n\tplace('start', first, 0);\n\tplace('end', last, contours.length - 1);\n\tif (!tipMode || !heads.length) return { align, line: path, drawWidth, heads };\n\tconst trimmed: BezierContour[] = contours.map((c, i) => {\n\t\tconst [s0, s1] = trims[i];\n\t\tif (!s0 && !s1) return c;\n\t\tconst lengths = c.segments.map(segLen);\n\t\tconst total = lengths.reduce((a, b) => a + b, 0);\n\t\treturn { closed: false, segments: total - s1 > s0 ? trimSegments(c.segments, s0, total - s1, lengths) : [] };\n\t});\n\treturn { align, line: contoursToPath(trimmed), drawWidth, heads };\n}\n\n/**\n * The area the stroke paints, as one filled path: the outlined stroke (joins, caps and dashes\n * as SVG) clipped by the alignment (against the fill, read with the style's `fillRule`), plus\n * the arrowheads.\n */\nexport function vectorStrokeOutlinePath(path: BezierPath, style: VectorStyle): BezierPath {\n\tconst parts = vectorStrokeParts(path, style);\n\tconst stroke = vectorStrokeLineArea(parts, style);\n\tconst fill: PathOperandInput = { path, fillRule: style.fillRule ?? 'nonzero' };\n\tlet area = stroke;\n\tif (parts.align === 'inside') area = pathBoolean('intersection', [stroke, fill]).path;\n\telse if (parts.align === 'outside') area = pathBoolean('difference', [stroke, fill]).path;\n\tif (!parts.heads.length) return area;\n\treturn pathBoolean('union', [area, ...parts.heads.map((h) => h.path)]).path;\n}\n\n/**\n * Whether point `p` hits the stroke of `path` (`tolerance` added on every side): within half\n * the width of the outline for a centred stroke, within the whole width on the inside or the\n * outside of the fill for an aligned one, or on an arrowhead.\n */\nexport function hitTestVectorStroke(path: BezierPath, p: Vec2, style: VectorStyle, tolerance = 0): boolean {\n\tconst parts = vectorStrokeParts(path, style);\n\tconst point: Point = [p.x, p.y];\n\tconst width = style.strokeWidth ?? 1;\n\tfor (const h of parts.heads) {\n\t\tif (pointInBezierPath(h.path, point) || (tolerance > 0 && distanceToBezierPath(h.path, point) <= tolerance)) return true;\n\t}\n\tif (!parts.line.length) return false;\n\tconst profile = strokeWidthProfileOf(style);\n\tlet d: number;\n\tlet reach = width / 2;\n\tif (profile) {\n\t\t// The side width at the nearest point of the centre line (twice it when aligned).\n\t\tconst near = nearestStrokePosition(parts.line, point);\n\t\tif (!near) return false;\n\t\td = near.distance;\n\t\tconst w = widthProfileAt(profile, near.t);\n\t\treach = (width / 2) * (near.side === 'left' ? w.left : w.right);\n\t\tif (parts.align !== 'center') reach *= 2;\n\t\tconst atEnd = near.t <= 1e-9 || near.t >= 1 - 1e-9;\n\t\tif (atEnd && (style.strokeLinecap === 'round' || style.strokeLinecap === 'square') && d > reach) {\n\t\t\t// Past an open end the cap reaches as far as the width there.\n\t\t\treach = Math.max(reach, ((width / 2) * (w.left + w.right)) / 2);\n\t\t}\n\t} else {\n\t\td = distanceToBezierPath(parts.line, point);\n\t\tif (parts.align !== 'center') reach = width;\n\t}\n\tif (parts.align === 'center') return d <= reach + tolerance;\n\tif (d > reach + tolerance) return false;\n\tif (d <= tolerance) return true;\n\tconst inside = pointInBezierPath(path, point, style.fillRule ?? 'nonzero');\n\treturn parts.align === 'inside' ? inside : !inside;\n}\n\n/** How far the stroke can reach past the outline (for padding bounds and masks). */\nexport function vectorStrokeReach(style: VectorStyle): number {\n\tconst width = (style.strokeWidth ?? 1) * widthProfileMaxScale(strokeWidthProfileOf(style));\n\tconst align = style.strokeAlign ?? 'center';\n\tlet reach = align === 'center' ? width / 2 : align === 'outside' ? width : 0;\n\tif (style.strokeLinejoin !== 'round' && style.strokeLinejoin !== 'bevel') reach *= 4;\n\tfor (const end of ['start', 'end'] as const) {\n\t\tif (end === 'start' ? style.startArrow : style.endArrow) reach = Math.max(reach, 8 * arrowheadWeight(style, end) + width);\n\t}\n\treturn reach;\n}\n","/**\n * Fill and stroke stacks: several paints per path, each with its own opacity, blend mode and\n * visibility, and each stroke with its own width, alignment, dashes, arrowheads and profile.\n *\n * The stacks live in `fills` / `strokes`, bottom to top; fills draw under strokes. The single\n * `fill` / `stroke` fields (and the stroke settings) stay as shorthands that mirror the top layer\n * of each stack, so everything written against one fill and one stroke — booleans, blends, the\n * gradient tool, agent commands, old documents — keeps working:\n *\n * - A node without `fills` has the one-layer stack its `fill` says (none without a paint); the\n * same for strokes. Stacks of one plain layer are stored that way, so old documents and simple\n * nodes look exactly as before.\n * - `normalizeVectorPaintStacks` reconciles the two forms. Loading prefers the stacks (the\n * shorthand is re-derived); editing prefers the shorthand: a node whose `fill` no longer matches\n * its top fill was written by shorthand code, so the top layer takes the new paint (`null`\n * clears the stack). The controller runs it after every change.\n *\n * Paint layers are drawn, hit-tested and exported through `vectorPaintLayerNodes`: one plain path\n * node per layer (the node with only that fill, or only that stroke and its settings), so the\n * existing paint, stroke-part and gradient code serves each layer unchanged.\n *\n * The data model follows figcraft `crates/color/src/lib.rs` (`Paint { kind, opacity, visible,\n * blend }` in a list per node) and vectorcraft `crates/doc/src/appearance.rs` (`FillLayer` /\n * `StrokeLayer`, each stroke with its own settings). Ported from ArtCraft (MIT OR Apache-2.0,\n * © ArtCraft Team and contributors; see THIRD_PARTY_NOTICES.md).\n */\nimport { pointInBezierPath, distanceToBezierPath, type BezierPath } from '../canvas/pathops/bezier.js';\nimport { isVectorGradient, sanitizeVectorGradient } from './gradient.js';\nimport { hitTestVectorStroke, vectorStrokeReach } from './stroke.js';\nimport { sanitizeWidthProfile } from './width.js';\nimport {\n\tVECTOR_ARROWHEADS,\n\tVECTOR_BLEND_MODES,\n\ttype VectorArrowhead,\n\ttype VectorBlendMode,\n\ttype VectorPaint,\n\ttype VectorPaintLayer,\n\ttype VectorPathNode,\n\ttype VectorStrokeLayer,\n\ttype VectorStrokeSettings,\n\ttype VectorStyle,\n\ttype Vec2\n} from './types.js';\n\n/** The stroke settings each stroke layer has of its own, in `VectorStyle` field names. */\nexport const VECTOR_STROKE_SETTING_KEYS: readonly (keyof VectorStrokeSettings)[] = [\n\t'strokeWidth',\n\t'strokeLinecap',\n\t'strokeLinejoin',\n\t'strokeDasharray',\n\t'strokeAlign',\n\t'startArrow',\n\t'endArrow',\n\t'arrowAlign',\n\t'startArrowScale',\n\t'endArrowScale',\n\t'strokeWidthProfile'\n];\n\nexport const VECTOR_BLEND_MODE_LABELS: Readonly<Record<VectorBlendMode, string>> = {\n\tnormal: 'Normal',\n\tdarken: 'Darken',\n\tmultiply: 'Multiply',\n\t'color-burn': 'Color Burn',\n\tlighten: 'Lighten',\n\tscreen: 'Screen',\n\t'color-dodge': 'Color Dodge',\n\t'linear-dodge': 'Linear Dodge (Add)',\n\toverlay: 'Overlay',\n\t'soft-light': 'Soft Light',\n\t'hard-light': 'Hard Light',\n\tdifference: 'Difference',\n\texclusion: 'Exclusion',\n\thue: 'Hue',\n\tsaturation: 'Saturation',\n\tcolor: 'Color',\n\tluminosity: 'Luminosity'\n};\n\nexport function isVectorBlendMode(value: unknown): value is VectorBlendMode {\n\treturn typeof value === 'string' && (VECTOR_BLEND_MODES as readonly string[]).includes(value);\n}\n\n/** A blend mode as a CSS `mix-blend-mode` value, or undefined for `normal` (nothing to write). */\nexport function vectorBlendModeCss(mode: VectorBlendMode | null | undefined): string | undefined {\n\tif (!mode || mode === 'normal' || !isVectorBlendMode(mode)) return undefined;\n\treturn mode === 'linear-dodge' ? 'plus-lighter' : mode;\n}\n\n/** A CSS `mix-blend-mode` value back to a blend mode (`plus-lighter` is `linear-dodge`), or null when SVG offers it but we don't. */\nexport function vectorBlendModeFromCss(value: string | null | undefined): VectorBlendMode | null {\n\tconst v = (value ?? '').trim().toLowerCase();\n\tif (v === 'plus-lighter') return 'linear-dodge';\n\treturn isVectorBlendMode(v) ? v : null;\n}\n\n/** True when the paint draws something (not `null`, `'none'`, `'transparent'` or empty). */\nconst draws = (paint: VectorPaint | undefined): boolean => {\n\tif (paint === undefined || paint === null) return false;\n\tif (typeof paint === 'string') {\n\t\tconst p = paint.trim().toLowerCase();\n\t\treturn p !== '' && p !== 'none' && p !== 'transparent';\n\t}\n\treturn true;\n};\n\n/** A layer with full opacity, `normal` blending, visible and (strokes) a plain paint: what the shorthand alone can say. */\nexport function isPlainVectorPaintLayer(layer: VectorPaintLayer | VectorStrokeLayer): boolean {\n\treturn (\n\t\t(layer.opacity ?? 1) >= 1 &&\n\t\t(layer.blendMode ?? 'normal') === 'normal' &&\n\t\tlayer.visible !== false &&\n\t\t((layer as VectorStrokeLayer).kind ?? 'paint') === 'paint'\n\t);\n}\n\n/** Whether a layer is drawn: visible, a plain paint (strokes), a paint that draws something, above zero opacity. */\nexport function isDrawnVectorPaintLayer(layer: VectorPaintLayer | VectorStrokeLayer): boolean {\n\treturn layer.visible !== false && ((layer as VectorStrokeLayer).kind ?? 'paint') === 'paint' && draws(layer.paint) && (layer.opacity ?? 1) > 0;\n}\n\n/** The fill stack of a path (or style), bottom to top: `fills`, or the one layer `fill` makes. */\nexport function vectorFills(style: VectorStyle): VectorPaintLayer[] {\n\tif (Array.isArray(style.fills)) return style.fills;\n\treturn draws(style.fill) ? [{ paint: style.fill! }] : [];\n}\n\n/** The stroke settings a style gives its shorthand stroke (only those set). */\nexport function vectorStrokeSettings(style: VectorStyle): VectorStrokeSettings {\n\tconst out: VectorStrokeSettings = {};\n\tfor (const key of VECTOR_STROKE_SETTING_KEYS) {\n\t\tconst v = style[key];\n\t\tif (v !== undefined && v !== null) (out as Record<string, unknown>)[key] = v;\n\t}\n\treturn out;\n}\n\n/** The stroke stack of a path (or style), bottom to top: `strokes`, or the one layer `stroke` and the stroke settings make. */\nexport function vectorStrokes(style: VectorStyle): VectorStrokeLayer[] {\n\tif (Array.isArray(style.strokes)) return style.strokes;\n\treturn draws(style.stroke) ? [{ paint: style.stroke!, ...vectorStrokeSettings(style) }] : [];\n}\n\n/** Whether a style stores a stack (more than its shorthand can say). */\nexport function hasVectorPaintStacks(style: VectorStyle): boolean {\n\treturn Array.isArray(style.fills) || Array.isArray(style.strokes);\n}\n\n/** The node with `fills` as its fill stack, stored canonically (see the module comment). */\nexport function withVectorFills<T extends VectorStyle>(node: T, fills: readonly VectorPaintLayer[]): T {\n\tconst next = { ...node };\n\tif (fills.length === 0 || (fills.length === 1 && isPlainVectorPaintLayer(fills[0]))) {\n\t\tdelete next.fills;\n\t\tnext.fill = fills[0]?.paint ?? null;\n\t\treturn next;\n\t}\n\tnext.fills = fills.map((l) => ({ ...l }));\n\tnext.fill = fills[fills.length - 1].paint;\n\treturn next;\n}\n\n/** The node with `strokes` as its stroke stack, stored canonically; the shorthand mirrors the top layer. */\nexport function withVectorStrokes<T extends VectorStyle>(node: T, strokes: readonly VectorStrokeLayer[]): T {\n\tconst next = { ...node };\n\tif (strokes.length === 0) {\n\t\t// The settings stay, for a stroke added later through the shorthand.\n\t\tdelete next.strokes;\n\t\tnext.stroke = null;\n\t\treturn next;\n\t}\n\tconst top = strokes[strokes.length - 1];\n\tfor (const key of VECTOR_STROKE_SETTING_KEYS) delete next[key];\n\tObject.assign(next, vectorStrokeSettings(top as VectorStyle));\n\tnext.stroke = top.paint;\n\tif (strokes.length === 1 && isPlainVectorPaintLayer(top)) delete next.strokes;\n\telse next.strokes = strokes.map((l) => ({ ...l }));\n\treturn next;\n}\n\nconst json = (v: unknown) => JSON.stringify(v ?? null);\n\n/** The stroke fields of `node` that differ from what its top stroke layer says. */\nfunction strokeShorthandDiff(node: VectorStyle, top: VectorStrokeLayer | undefined): (keyof VectorStrokeSettings | 'stroke')[] {\n\tconst out: (keyof VectorStrokeSettings | 'stroke')[] = [];\n\tif (json(node.stroke) !== json(top?.paint)) out.push('stroke');\n\tfor (const key of VECTOR_STROKE_SETTING_KEYS) if (json(node[key]) !== json(top?.[key])) out.push(key);\n\treturn out;\n}\n\nconst checked = new WeakSet<object>();\n\n/**\n * A node with its stacks and shorthands reconciled and stored canonically (the same object when\n * they already are). `prefer: 'stack'` (loading) re-derives the shorthand from the stacks;\n * `'shorthand'` (after an edit, the default) reads a shorthand that disagrees with its stack's\n * top layer as an edit of that layer: a paint repaints it, a stroke setting changes it, `null`\n * clears the stack.\n */\nexport function normalizeVectorPaintStacks<T extends VectorStyle>(node: T, prefer: 'stack' | 'shorthand' = 'shorthand'): T {\n\tif (!hasVectorPaintStacks(node)) {\n\t\tif (!('fills' in node) && !('strokes' in node)) return node;\n\t\t// `fills: null` / `strokes: null`: no stack.\n\t\tconst next = { ...node };\n\t\tdelete next.fills;\n\t\tdelete next.strokes;\n\t\treturn next;\n\t}\n\tif (checked.has(node)) return node;\n\tlet next: T = node;\n\tif (Array.isArray(node.fills)) {\n\t\tlet fills: VectorPaintLayer[] = node.fills;\n\t\tconst top = fills[fills.length - 1];\n\t\tif (prefer === 'shorthand' && json(node.fill) !== json(top?.paint)) {\n\t\t\tif (!draws(node.fill)) fills = [];\n\t\t\telse fills = top ? [...fills.slice(0, -1), { ...top, paint: node.fill! }] : [{ paint: node.fill! }];\n\t\t}\n\t\tnext = withVectorFills(next, fills);\n\t}\n\tif (Array.isArray(node.strokes)) {\n\t\tlet strokes: VectorStrokeLayer[] = node.strokes;\n\t\tconst top = strokes[strokes.length - 1];\n\t\tconst diff = prefer === 'shorthand' ? strokeShorthandDiff(node, top) : [];\n\t\tif (diff.length) {\n\t\t\tif (diff.includes('stroke') && !draws(node.stroke)) strokes = [];\n\t\t\telse {\n\t\t\t\tconst layer: VectorStrokeLayer = top ? { ...top } : { paint: node.stroke ?? null };\n\t\t\t\tfor (const key of diff) {\n\t\t\t\t\tif (key === 'stroke') layer.paint = node.stroke ?? null;\n\t\t\t\t\telse if (node[key] === undefined || node[key] === null) delete layer[key];\n\t\t\t\t\telse (layer as unknown as Record<string, unknown>)[key] = node[key];\n\t\t\t\t}\n\t\t\t\tstrokes = top ? [...strokes.slice(0, -1), layer] : [layer];\n\t\t\t}\n\t\t}\n\t\tnext = withVectorStrokes(next, strokes);\n\t}\n\tconst keys = ['fill', 'fills', 'stroke', 'strokes', ...VECTOR_STROKE_SETTING_KEYS] as const;\n\tconst same = keys.every((k) => k in next === k in node && json(next[k]) === json(node[k]));\n\tconst out = same ? node : next;\n\tchecked.add(out);\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Sanitizing\n// ---------------------------------------------------------------------------\n\nfunction sanitizePaint(value: unknown): VectorPaint {\n\tif (value === null || value === undefined) return null;\n\tif (typeof value === 'string') return value;\n\treturn sanitizeVectorGradient(value);\n}\n\nfunction sanitizeLayerBase(raw: Record<string, unknown>): VectorPaintLayer {\n\tconst out: VectorPaintLayer = { paint: sanitizePaint(raw.paint) };\n\tif (typeof raw.opacity === 'number' && Number.isFinite(raw.opacity) && raw.opacity < 1) out.opacity = Math.max(0, raw.opacity);\n\tif (isVectorBlendMode(raw.blendMode) && raw.blendMode !== 'normal') out.blendMode = raw.blendMode;\n\tif (raw.visible === false) out.visible = false;\n\treturn out;\n}\n\n/** Reads a fill stack from untrusted data (JSON, agent input, SVG): malformed layers are dropped. */\nexport function sanitizeVectorFills(value: unknown): VectorPaintLayer[] {\n\tif (!Array.isArray(value)) return [];\n\treturn value.filter((l): l is Record<string, unknown> => !!l && typeof l === 'object').map(sanitizeLayerBase);\n}\n\nconst ARROW_ALIGNS = ['extend', 'tip'];\n\n/** Stroke settings from untrusted data: wrong types and out-of-range values are dropped. */\nexport function sanitizeVectorStrokeSettings(raw: Record<string, unknown>): VectorStrokeSettings {\n\tconst out: VectorStrokeSettings = {};\n\tconst num = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);\n\tconst width = num(raw.strokeWidth);\n\tif (width !== undefined && width >= 0) out.strokeWidth = width;\n\tif (raw.strokeLinecap === 'butt' || raw.strokeLinecap === 'round' || raw.strokeLinecap === 'square') out.strokeLinecap = raw.strokeLinecap;\n\tif (raw.strokeLinejoin === 'miter' || raw.strokeLinejoin === 'round' || raw.strokeLinejoin === 'bevel') out.strokeLinejoin = raw.strokeLinejoin;\n\tif (Array.isArray(raw.strokeDasharray)) {\n\t\tconst dash = raw.strokeDasharray.filter((n): n is number => typeof n === 'number' && Number.isFinite(n) && n >= 0);\n\t\tif (dash.length && dash.some((n) => n > 0)) out.strokeDasharray = dash;\n\t}\n\tif (raw.strokeAlign === 'center' || raw.strokeAlign === 'inside' || raw.strokeAlign === 'outside') out.strokeAlign = raw.strokeAlign;\n\tfor (const key of ['startArrow', 'endArrow'] as const) if (VECTOR_ARROWHEADS.includes(raw[key] as VectorArrowhead)) out[key] = raw[key] as VectorArrowhead;\n\tif (ARROW_ALIGNS.includes(raw.arrowAlign as string)) out.arrowAlign = raw.arrowAlign as 'extend' | 'tip';\n\tfor (const key of ['startArrowScale', 'endArrowScale'] as const) {\n\t\tconst n = num(raw[key]);\n\t\tif (n !== undefined && n > 0) out[key] = n;\n\t}\n\tif (Array.isArray(raw.strokeWidthProfile)) {\n\t\tconst p = sanitizeWidthProfile(raw.strokeWidthProfile as never);\n\t\tif (p.length) out.strokeWidthProfile = p;\n\t}\n\treturn out;\n}\n\n/**\n * Reads a stroke stack from untrusted data. Layers of a kind this version does not know are kept\n * as they are (a later version's brushstroke, say), so a round trip does not lose them.\n */\nexport function sanitizeVectorStrokes(value: unknown): VectorStrokeLayer[] {\n\tif (!Array.isArray(value)) return [];\n\treturn value\n\t\t.filter((l): l is Record<string, unknown> => !!l && typeof l === 'object')\n\t\t.map((raw) => {\n\t\t\tif (raw.kind !== undefined && raw.kind !== 'paint') return { ...(raw as unknown as VectorStrokeLayer) };\n\t\t\treturn { ...sanitizeLayerBase(raw), ...sanitizeVectorStrokeSettings(raw) };\n\t\t});\n}\n\n// ---------------------------------------------------------------------------\n// Layer nodes: one plain path per layer\n// ---------------------------------------------------------------------------\n\n/** One layer of a path's paint as a plain path node (see `vectorPaintLayerNodes`). */\nexport interface VectorPaintLayerNode {\n\tkind: 'fill' | 'stroke';\n\t/** Index in its stack, bottom to top. */\n\tindex: number;\n\tlayer: VectorPaintLayer | VectorStrokeLayer;\n\t/** The path with only this fill, or only this stroke and its settings (no stacks). */\n\tnode: VectorPathNode;\n\t/** Visible, a plain paint, painting something, above zero opacity. */\n\tdrawn: boolean;\n}\n\nconst layerNodeCache = new WeakMap<VectorPathNode, VectorPaintLayerNode[]>();\n\n/**\n * Every fill then every stroke of a path, bottom to top, each as the path painted by that layer\n * alone. Cached per node object (and the layer nodes are the same objects while it is), so the\n * paint and stroke-part caches keyed by node keep working per layer.\n */\nexport function vectorPaintLayerNodes(node: VectorPathNode): VectorPaintLayerNode[] {\n\tconst hit = layerNodeCache.get(node);\n\tif (hit) return hit;\n\tconst { fills: _f, strokes: _s, ...base } = node;\n\tconst plain = { ...base } as VectorPathNode;\n\tfor (const key of VECTOR_STROKE_SETTING_KEYS) delete plain[key];\n\tconst out: VectorPaintLayerNode[] = [];\n\tvectorFills(node).forEach((layer, index) => {\n\t\tout.push({ kind: 'fill', index, layer, node: { ...plain, fill: layer.paint, stroke: null }, drawn: isDrawnVectorPaintLayer(layer) });\n\t});\n\tvectorStrokes(node).forEach((layer, index) => {\n\t\tconst settings = (layer.kind ?? 'paint') === 'paint' ? vectorStrokeSettings(layer as VectorStyle) : {};\n\t\tout.push({ kind: 'stroke', index, layer, node: { ...plain, fill: null, ...settings, stroke: layer.paint }, drawn: isDrawnVectorPaintLayer(layer) });\n\t});\n\tlayerNodeCache.set(node, out);\n\treturn out;\n}\n\n/** Whether a path draws through its stacks rather than one fill and one stroke (several layers, or one with its own opacity, blend or visibility). */\nexport function needsVectorPaintLayers(node: VectorPathNode): boolean {\n\tif (!hasVectorPaintStacks(node)) return false;\n\tconst fills = vectorFills(node);\n\tconst strokes = vectorStrokes(node);\n\treturn fills.length > 1 || strokes.length > 1 || fills.some((l) => !isPlainVectorPaintLayer(l)) || strokes.some((l) => !isPlainVectorPaintLayer(l));\n}\n\n/** How far the drawn strokes of a path reach past its outline (the widest layer's reach; 0 without strokes). */\nexport function vectorStrokeStackReach(node: VectorPathNode): number {\n\tlet reach = 0;\n\tfor (const l of vectorPaintLayerNodes(node)) if (l.kind === 'stroke' && l.drawn) reach = Math.max(reach, vectorStrokeReach(l.node));\n\treturn reach;\n}\n\n/** The layer a point hits (see `hitTestVectorPaintLayer`). */\nexport interface VectorPaintLayerHit {\n\tkind: 'fill' | 'stroke';\n\tindex: number;\n}\n\n/**\n * The topmost drawn layer of a path that `p` hits, or null: strokes top to bottom (their\n * alignment, arrowheads and profiles respected, `tolerance` added), then fills. `path` is the\n * node's outline in the same space as `p` (`vectorPathWorldBezier`).\n */\nexport function hitTestVectorPaintLayer(node: VectorPathNode, path: BezierPath, p: Vec2, tolerance = 0): VectorPaintLayerHit | null {\n\tif (!path.length) return null;\n\tconst layers = vectorPaintLayerNodes(node);\n\tfor (let i = layers.length - 1; i >= 0; i--) {\n\t\tconst l = layers[i];\n\t\tif (!l.drawn) continue;\n\t\tif (l.kind === 'stroke') {\n\t\t\tif ((l.node.strokeWidth ?? 1) > 0 && hitTestVectorStroke(path, p, l.node, tolerance)) return { kind: 'stroke', index: l.index };\n\t\t} else if (pointInBezierPath(path, [p.x, p.y], node.fillRule ?? 'nonzero')) return { kind: 'fill', index: l.index };\n\t}\n\treturn null;\n}\n\n/**\n * Whether `p` hits a path as drawn by its stacks: on a drawn layer, or, when it draws no stroke,\n * within `tolerance` of its outline (so unpainted paths stay pickable).\n */\nexport function hitTestVectorPaintStack(node: VectorPathNode, path: BezierPath, p: Vec2, tolerance = 0): boolean {\n\tif (hitTestVectorPaintLayer(node, path, p, tolerance)) return true;\n\tif (vectorPaintLayerNodes(node).some((l) => l.kind === 'stroke' && l.drawn)) return false;\n\treturn distanceToBezierPath(path, [p.x, p.y]) <= tolerance;\n}\n\n/** Whether a fill or stroke stack holds a gradient anywhere (for transforms that move gradients). */\nexport function vectorStacksHaveGradient(style: VectorStyle): boolean {\n\treturn [...(style.fills ?? []), ...(style.strokes ?? [])].some((l) => isVectorGradient(l.paint));\n}\n\n/** Defaults of a new layer added to a stack (a stroke takes the settings of the stack's top stroke). */\nexport function newVectorStrokeLayer(node: VectorStyle, patch: Partial<VectorStrokeLayer> = {}): VectorStrokeLayer {\n\tconst top = vectorStrokes(node).at(-1);\n\tconst settings = top && (top.kind ?? 'paint') === 'paint' ? vectorStrokeSettings(top as VectorStyle) : { strokeWidth: node.strokeWidth ?? 1 };\n\treturn { paint: '#000000', ...settings, ...patch };\n}\n\n/**\n * The paint the gradient tool and editor work on: layer `layer` of the fill or stroke stack, or\n * (null, or an index the stack doesn't have) the shorthand, which is the top layer.\n */\nexport function vectorTargetPaint(style: VectorStyle, target: 'fill' | 'stroke', layer: number | null = null): VectorPaint | undefined {\n\tif (layer === null) return style[target];\n\tconst stack = target === 'fill' ? vectorFills(style) : vectorStrokes(style);\n\treturn stack[layer] ? stack[layer].paint : style[target];\n}\n\n/** The node with `paint` in the place `vectorTargetPaint` reads (stored canonically). */\nexport function withVectorTargetPaint<T extends VectorStyle>(node: T, target: 'fill' | 'stroke', layer: number | null, paint: VectorPaint): T {\n\tconst stack = target === 'fill' ? vectorFills(node) : vectorStrokes(node);\n\tif (layer === null || !stack[layer]) {\n\t\tif (!stack.length) return { ...node, [target]: paint };\n\t\tlayer = stack.length - 1;\n\t}\n\tconst next = stack.map((l, i) => (i === layer ? { ...l, paint } : l));\n\treturn target === 'fill' ? withVectorFills(node, next) : withVectorStrokes(node, next as VectorStrokeLayer[]);\n}\n","/**\n * Non-affine maps of paths, so shapes stay Bézier paths through Free Distort (a projective map)\n * and envelope warps.\n *\n * - `mapVectorPoints` sends every anchor and handle through the map and keeps the anchors as they\n * are. Under a projective map straight segments stay exactly straight; a curve keeps its exact\n * end tangents and approximates the (rational) image between.\n * - `mapVectorSubpaths` follows any smooth map: each segment is halved until its mapped control\n * points follow the mapped curve within a tolerance, so the result gains anchors where the map\n * bends it. Runs of pieces are then refitted with fewer cubics (`fitCubicRun`) where that stays\n * within the tolerance.\n *\n * Ported from ArtCraft (MIT OR Apache-2.0, © ArtCraft Team and contributors; see\n * THIRD_PARTY_NOTICES.md): photocraft `crates/vector/src/map.rs` (`map_points`, `map_path`,\n * `map_subpath`, `map_segment`, `split_mapped`, `halves`, `off_line`).\n */\nimport { fitCubicRun, type CubicSegment } from '../canvas/curveFit.js';\nimport { segmentNearest, type BezierPath, type BezierPathInput } from '../canvas/pathops/bezier.js';\nimport { bezierToSubpaths, subpathsToBezier } from './geometry.js';\nimport type { Vec2, VectorAnchor, VectorSubpath } from './types.js';\n\n/** A map of the plane (document or node space). */\nexport type VectorPointMap = (p: Vec2) => Vec2;\n\ntype Cubic = [Vec2, Vec2, Vec2, Vec2];\n\n/** Pieces a path may be split into in total: bounds the work on hostile input. */\nconst MAX_PIECES = 1 << 20;\n/** Deepest split of one segment (2^6 = 64 pieces). */\nconst MAX_DEPTH = 6;\nconst PROBES = [0.25, 0.5, 0.75];\n\nconst lerp = (a: Vec2, b: Vec2, t: number): Vec2 => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });\nconst dist = (a: Vec2, b: Vec2) => Math.hypot(a.x - b.x, a.y - b.y);\nconst same = (a: Vec2, b: Vec2) => Math.abs(a.x - b.x) < 1e-9 && Math.abs(a.y - b.y) < 1e-9;\n\nfunction evalCubic(s: Cubic, t: number): Vec2 {\n\tconst ab = lerp(s[0], s[1], t);\n\tconst bc = lerp(s[1], s[2], t);\n\tconst cd = lerp(s[2], s[3], t);\n\treturn lerp(lerp(ab, bc, t), lerp(bc, cd, t), t);\n}\n\n/** De Casteljau split at t = ½. */\nfunction halves(s: Cubic): [Cubic, Cubic] {\n\tconst ab = lerp(s[0], s[1], 0.5);\n\tconst bc = lerp(s[1], s[2], 0.5);\n\tconst cd = lerp(s[2], s[3], 0.5);\n\tconst abc = lerp(ab, bc, 0.5);\n\tconst bcd = lerp(bc, cd, 0.5);\n\tconst m = lerp(abc, bcd, 0.5);\n\treturn [\n\t\t[s[0], ab, abc, m],\n\t\t[m, bcd, cd, s[3]]\n\t];\n}\n\n/** Distance from `p` to the line through `a` and `b` (to `a` when they coincide). */\nfunction offLine(p: Vec2, a: Vec2, b: Vec2): number {\n\tconst l = dist(a, b);\n\treturn l < 1e-12 ? dist(p, a) : Math.abs((b.x - a.x) * (a.y - p.y) - (a.x - p.x) * (b.y - a.y)) / l;\n}\n\nconst mapAnchorPoints = (a: VectorAnchor, f: VectorPointMap): VectorAnchor => {\n\tconst p = f(a);\n\treturn {\n\t\tx: p.x,\n\t\ty: p.y,\n\t\t...(a.in ? { in: f(a.in) } : {}),\n\t\t...(a.out ? { out: f(a.out) } : {}),\n\t\t...(a.type && a.type !== 'corner' ? { type: a.type } : {}),\n\t\t...(a.corner ? { corner: { ...a.corner } } : {})\n\t};\n};\n\n/** Every anchor and handle of `subpaths` through `f` (no subdivision). */\nexport function mapVectorPoints(subpaths: readonly VectorSubpath[], f: VectorPointMap): VectorSubpath[] {\n\treturn subpaths.map((sp) => ({ closed: sp.closed, anchors: sp.anchors.map((a) => mapAnchorPoints(a, f)) }));\n}\n\nfunction splitMapped(seg: Cubic, f: VectorPointMap, tol: number, depth: number, out: Cubic[]) {\n\tconst mapped = seg.map(f) as Cubic;\n\tconst fits = PROBES.every((t) => dist(f(evalCubic(seg, t)), evalCubic(mapped, t)) <= tol);\n\tif (fits || depth === 0) {\n\t\tout.push(mapped);\n\t\treturn;\n\t}\n\tconst [a, b] = halves(seg);\n\tsplitMapped(a, f, tol, depth - 1, out);\n\tsplitMapped(b, f, tol, depth - 1, out);\n}\n\nconst toPoint = (p: Vec2): [number, number] => [p.x, p.y];\nconst toVec = (p: readonly number[]): Vec2 => ({ x: p[0], y: p[1] });\n\n/**\n * The image of `seg` through `f` as fewer cubics than `pieces` when they stay within `tol` of it\n * (fitted to samples of the mapped curve with the pieces' end tangents), else `pieces`.\n */\nfunction refit(seg: Cubic, pieces: Cubic[], f: VectorPointMap, tol: number): Cubic[] {\n\tif (pieces.length < 3) return pieces;\n\tconst n = pieces.length * 4;\n\tconst samples: [number, number][] = [];\n\tfor (let i = 0; i <= n; i++) samples.push(toPoint(f(evalCubic(seg, i / n))));\n\tconst first = pieces[0];\n\tconst last = pieces[pieces.length - 1];\n\tconst t0 = !same(first[1], first[0]) ? first[1] : first[2];\n\tconst t1 = !same(last[2], last[3]) ? last[2] : last[1];\n\tconst fitted: CubicSegment[] = fitCubicRun(samples, tol / 2, [t0.x - first[0].x, t0.y - first[0].y], [last[3].x - t1.x, last[3].y - t1.y]);\n\tif (!fitted.length || fitted.length >= pieces.length) return pieces;\n\t// Check between the fitted samples too.\n\tconst chain = fitted.map((c) => [c.p0, c.p1, c.p2, c.p3]);\n\tfor (let i = 0; i < n; i++) {\n\t\tconst q = toPoint(f(evalCubic(seg, (i + 0.5) / n)));\n\t\tlet best = Infinity;\n\t\tfor (const c of chain) best = Math.min(best, segmentNearest(c, q).distance);\n\t\tif (best > tol) return pieces;\n\t}\n\treturn fitted.map((c) => [toVec(c.p0), toVec(c.p1), toVec(c.p2), toVec(c.p3)]);\n}\n\nfunction mapSegment(seg: Cubic, f: VectorPointMap, tol: number, depth: number, fit: boolean): Cubic[] {\n\t// A straight segment whose image is straight stays one.\n\tif (same(seg[1], seg[0]) && same(seg[2], seg[3])) {\n\t\tconst a = f(seg[0]);\n\t\tconst b = f(seg[3]);\n\t\tif (PROBES.every((t) => offLine(f(lerp(seg[0], seg[3], t)), a, b) <= tol)) return [[a, a, b, b]];\n\t\t// Bent by the map: the same line as an evenly parametrised cubic, then split.\n\t\tconst line: Cubic = [seg[0], lerp(seg[0], seg[3], 1 / 3), lerp(seg[0], seg[3], 2 / 3), seg[3]];\n\t\tconst out: Cubic[] = [];\n\t\tsplitMapped(line, f, tol, depth, out);\n\t\treturn fit ? refit(line, out, f, tol) : out;\n\t}\n\tconst out: Cubic[] = [];\n\tsplitMapped(seg, f, tol, depth, out);\n\treturn fit ? refit(seg, out, f, tol) : out;\n}\n\n/** Makes a smooth anchor's handles collinear again (a curved map can kink them slightly), keeping their lengths. */\nfunction realign(a: VectorAnchor): VectorAnchor {\n\tif (!a.in || !a.out || same(a.in, a) || same(a.out, a)) return a;\n\tconst li = dist(a.in, a);\n\tconst lo = dist(a.out, a);\n\tconst dx = (a.x - a.in.x) / li + (a.out.x - a.x) / lo;\n\tconst dy = (a.y - a.in.y) / li + (a.out.y - a.y) / lo;\n\tconst l = Math.hypot(dx, dy);\n\tif (l < 1e-12) return a;\n\tconst u = { x: dx / l, y: dy / l };\n\tconst type = a.type === 'symmetric' && Math.abs(li - lo) > 1e-6 * Math.max(li, lo) ? 'smooth' : a.type;\n\treturn { ...a, in: { x: a.x - u.x * li, y: a.y - u.y * li }, out: { x: a.x + u.x * lo, y: a.y + u.y * lo }, ...(type ? { type } : {}) };\n}\n\nconst segmentOf = (sp: VectorSubpath, i: number): Cubic => {\n\tconst a = sp.anchors[i];\n\tconst b = sp.anchors[(i + 1) % sp.anchors.length];\n\treturn [{ x: a.x, y: a.y }, a.out ?? { x: a.x, y: a.y }, b.in ?? { x: b.x, y: b.y }, { x: b.x, y: b.y }];\n};\n\nfunction mapSubpath(sp: VectorSubpath, f: VectorPointMap, tol: number, depth: number, fit: boolean): VectorSubpath {\n\tconst n = sp.anchors.length;\n\tconst count = n < 2 ? 0 : sp.closed ? n : n - 1;\n\tif (!count) return mapVectorPoints([sp], f)[0];\n\tconst pieces: Cubic[][] = [];\n\tfor (let i = 0; i < count; i++) pieces.push(mapSegment(segmentOf(sp, i), f, tol, depth, fit));\n\tconst anchors: VectorAnchor[] = [];\n\tconst put = (a: VectorAnchor) => {\n\t\tif (a.in && same(a.in, a)) delete a.in;\n\t\tif (a.out && same(a.out, a)) delete a.out;\n\t\tanchors.push(a.type === 'smooth' || a.type === 'symmetric' ? realign(a) : a);\n\t};\n\tsp.anchors.forEach((k, i) => {\n\t\tconst incoming = i > 0 ? pieces[i - 1] : sp.closed ? pieces[n - 1] : undefined;\n\t\tconst outgoing = pieces[i];\n\t\tconst p = f(k);\n\t\tconst a: VectorAnchor = { x: p.x, y: p.y };\n\t\tconst inCtrl = incoming ? incoming[incoming.length - 1][2] : k.in ? f(k.in) : null;\n\t\tconst outCtrl = outgoing ? outgoing[0][1] : k.out ? f(k.out) : null;\n\t\tif (inCtrl) a.in = inCtrl;\n\t\tif (outCtrl) a.out = outCtrl;\n\t\tif (k.type && k.type !== 'corner') a.type = k.type;\n\t\tif (k.corner) a.corner = { ...k.corner };\n\t\tput(a);\n\t\t// The anchors a split added inside segment i: smooth, as the curve runs through them.\n\t\tif (outgoing) for (let w = 1; w < outgoing.length; w++) put({ x: outgoing[w][0].x, y: outgoing[w][0].y, in: outgoing[w - 1][2], out: outgoing[w][1], type: 'smooth' });\n\t});\n\treturn { closed: sp.closed, anchors };\n}\n\nexport interface MapVectorSubpathsOptions {\n\t/** Refit runs of split pieces with fewer cubics where they stay within the tolerance (default true). */\n\trefit?: boolean;\n}\n\n/**\n * `subpaths` through a smooth, possibly non-linear map `f`: each segment is split in halves until\n * its mapped control points follow the mapped curve within `tolerance` (at most 64 pieces a\n * segment, fewer for huge paths), then refitted with fewer cubics where possible. A straight\n * segment whose image is straight stays a straight segment; anchors keep their types and live\n * corners, and the anchors added are smooth.\n */\nexport function mapVectorSubpaths(subpaths: readonly VectorSubpath[], f: VectorPointMap, tolerance = 0.1, options: MapVectorSubpathsOptions = {}): VectorSubpath[] {\n\tconst tol = Number.isFinite(tolerance) && tolerance > 0 ? tolerance : 0.1;\n\tconst segments = subpaths.reduce((s, sp) => s + sp.anchors.length, 0);\n\tlet depth = MAX_DEPTH;\n\twhile (depth > 0 && segments * 2 ** depth > MAX_PIECES) depth--;\n\treturn subpaths.map((sp) => mapSubpath(sp, f, tol, depth, options.refit !== false));\n}\n\n/** `mapVectorSubpaths` for Bézier commands (live corners are not involved). */\nexport function mapBezierPath(path: BezierPathInput, f: VectorPointMap, tolerance = 0.1, options: MapVectorSubpathsOptions = {}): BezierPath {\n\treturn subpathsToBezier(mapVectorSubpaths(bezierToSubpaths(path), f, tolerance, options), { corners: false });\n}\n","/**\n * Knife and eraser on Bézier paths, built on the curve booleans and `outlineStroke`.\n *\n * The knife cuts filled regions along a polyline with no gap: each stretch of the polyline\n * that crosses a region from boundary to boundary (a chord) splits the boundary loop it starts\n * and ends on into two loops sharing that chord, or, when it joins two loops (an outline and a\n * hole), merges them into one. Crossings are exact (line–cubic roots), and the pieces share\n * the cut edge exactly.\n *\n * The eraser removes the area of a round-capped stroke along a polyline: filled shapes lose it\n * by a boolean difference; outlines are cut where they cross the stroke's outline (exact\n * curve–curve intersections) and keep the parts outside it.\n *\n * Modelled on vectorcraft-main/crates/engine/src/cmd/draw2.rs (`knife`, `erase_region`,\n * `erase_outline`, `cut_subpath`; MIT OR Apache-2.0, © ArtCraft Team and contributors; see\n * THIRD_PARTY_NOTICES.md), without its fixed knife slit and its sampled crossing search.\n */\nimport type { Point } from '../canvas/types.js';\nimport {\n\tbezierPathBounds,\n\tcloseContour,\n\tcontourSignedArea,\n\tcontoursToPath,\n\tpathToContours,\n\tpointInBezierPath,\n\tsegmentBounds,\n\tsegmentPoint,\n\tsubSegment,\n\twindingNumber,\n\ttype BezierPath,\n\ttype BezierPathInput\n} from '../canvas/pathops/bezier.js';\nimport { normalizeBezierPath, pathBoolean, segmentIntersections } from '../canvas/pathops/boolean.js';\nimport { outlineStroke } from '../canvas/pathops/stroke.js';\nimport { inferAnchorType, subpathSegment, subpathSegmentCount } from './geometry.js';\nimport type { VectorAnchor, VectorSubpath } from './types.js';\n\ntype FillRule = 'nonzero' | 'evenodd';\n\n// ---------------------------------------------------------------------------\n// Faces: an outer loop with its holes\n// ---------------------------------------------------------------------------\n\ninterface Loop {\n\tsegs: Point[][];\n\t/** Which segments came from a knife chord (not sampled for containment). */\n\tchord: boolean[];\n}\n\ninterface Face {\n\tloops: Loop[];\n}\n\nconst loopArea = (l: Loop) => contourSignedArea(l.segs);\n\n/** A point on the loop that is not on a chord (chords are shared with a neighbouring piece). */\nfunction loopSample(l: Loop): Point {\n\tconst k = l.chord.findIndex((c) => !c);\n\treturn segmentPoint(l.segs[k >= 0 ? k : 0], 0.5);\n}\n\nconst contains = (l: Loop, p: Point) => windingNumber([l.segs], p) !== 0;\n\n/**\n * Groups non-crossing loops into faces: loops turning like the largest one are outlines, the\n * others holes of the smallest outline around them.\n */\nfunction groupFaces(loops: Loop[]): Face[] {\n\tconst usable = loops.filter((l) => l.segs.length && Math.abs(loopArea(l)) > 1e-12);\n\tif (!usable.length) return [];\n\tconst largest = usable.reduce((a, b) => (Math.abs(loopArea(b)) > Math.abs(loopArea(a)) ? b : a));\n\tconst sign = Math.sign(loopArea(largest));\n\tconst outers = usable.filter((l) => Math.sign(loopArea(l)) === sign).sort((a, b) => Math.abs(loopArea(a)) - Math.abs(loopArea(b)));\n\tconst faces = new Map<Loop, Face>(outers.map((o) => [o, { loops: [o] }]));\n\tfor (const hole of usable.filter((l) => Math.sign(loopArea(l)) !== sign)) {\n\t\tconst p = loopSample(hole);\n\t\tconst parent = outers.find((o) => Math.abs(loopArea(o)) > Math.abs(loopArea(hole)) && contains(o, p));\n\t\tif (parent) faces.get(parent)!.loops.push(hole);\n\t}\n\treturn outers.reverse().map((o) => faces.get(o)!);\n}\n\nfunction faceToPath(face: Face): BezierPath {\n\treturn contoursToPath(face.loops.map((l) => ({ segments: l.segs, closed: true })));\n}\n\n/** Simple, consistently oriented faces of a filled path. */\nfunction facesOf(path: BezierPathInput, fillRule: FillRule): Face[] {\n\tconst normalized = normalizeBezierPath(path, fillRule);\n\tconst loops = pathToContours(normalized).map((c) => {\n\t\tconst segs = closeContour(c, true).map((s) => s.slice());\n\t\treturn { segs, chord: segs.map(() => false) };\n\t});\n\treturn groupFaces(loops);\n}\n\n/** One path per face (an outline with its holes) of a filled path, largest first. */\nexport function splitIntoFaces(path: BezierPathInput, fillRule: FillRule = 'nonzero'): BezierPath[] {\n\treturn facesOf(path, fillRule).map(faceToPath);\n}\n\n// ---------------------------------------------------------------------------\n// Line × segment crossings\n// ---------------------------------------------------------------------------\n\n/** Real roots in [0, 1] of `a·t³ + b·t² + c·t + d` (monotone pieces, bisection). */\nfunction rootsInUnit(a: number, b: number, c: number, d: number, scale: number): number[] {\n\tconst f = (t: number) => ((a * t + b) * t + c) * t + d;\n\tconst tol = 1e-12 * Math.max(1, scale);\n\t// Critical points split [0, 1] into monotone pieces.\n\tconst cuts = [0];\n\tconst qa = 3 * a;\n\tconst qb = 2 * b;\n\tif (Math.abs(qa) > 1e-18) {\n\t\tconst disc = qb * qb - 4 * qa * c;\n\t\tif (disc >= 0) {\n\t\t\tconst r = Math.sqrt(disc);\n\t\t\tfor (const t of [(-qb - r) / (2 * qa), (-qb + r) / (2 * qa)].sort((x, y) => x - y)) if (t > 0 && t < 1) cuts.push(t);\n\t\t}\n\t} else if (Math.abs(qb) > 1e-18) {\n\t\tconst t = -c / qb;\n\t\tif (t > 0 && t < 1) cuts.push(t);\n\t}\n\tcuts.push(1);\n\tconst roots: number[] = [];\n\tconst push = (t: number) => {\n\t\tif (!roots.some((r) => Math.abs(r - t) < 1e-9)) roots.push(t);\n\t};\n\tfor (let i = 0; i < cuts.length - 1; i++) {\n\t\tlet lo = cuts[i];\n\t\tlet hi = cuts[i + 1];\n\t\tlet flo = f(lo);\n\t\tconst fhi = f(hi);\n\t\tif (Math.abs(flo) <= tol) push(lo);\n\t\tif (Math.abs(fhi) <= tol) push(hi);\n\t\tif (flo * fhi >= 0) continue;\n\t\tfor (let k = 0; k < 80; k++) {\n\t\t\tconst m = (lo + hi) / 2;\n\t\t\tconst fm = f(m);\n\t\t\tif (fm === 0) {\n\t\t\t\tlo = hi = m;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (fm * flo < 0) hi = m;\n\t\t\telse {\n\t\t\t\tlo = m;\n\t\t\t\tflo = fm;\n\t\t\t}\n\t\t}\n\t\tpush((lo + hi) / 2);\n\t}\n\treturn roots.sort((x, y) => x - y);\n}\n\n/** Where the line `p → q` crosses a segment: `s` on the line (0–1), `t` on the segment. Parallel overlaps are not crossings. */\nfunction lineHits(p: Point, q: Point, seg: readonly Point[]): { s: number; t: number; point: Point }[] {\n\tconst dx = q[0] - p[0];\n\tconst dy = q[1] - p[1];\n\tconst len2 = dx * dx + dy * dy;\n\tif (len2 < 1e-24) return [];\n\t// Signed distance (times |d|) of the segment from the line, as a polynomial in t.\n\tconst h = (pt: Point) => (pt[0] - p[0]) * -dy + (pt[1] - p[1]) * dx;\n\tlet coeffs: [number, number, number, number];\n\tif (seg.length === 2) coeffs = [0, 0, h(seg[1]) - h(seg[0]), h(seg[0])];\n\telse {\n\t\tconst [h0, h1, h2, h3] = seg.map(h);\n\t\tcoeffs = [-h0 + 3 * h1 - 3 * h2 + h3, 3 * h0 - 6 * h1 + 3 * h2, -3 * h0 + 3 * h1, h0];\n\t}\n\tif (coeffs.every((c) => Math.abs(c) < 1e-12 * Math.max(1, len2))) return [];\n\tconst scale = Math.sqrt(len2) * Math.max(1, ...seg.map((pt) => Math.abs(pt[0]) + Math.abs(pt[1])));\n\tconst out: { s: number; t: number; point: Point }[] = [];\n\tfor (const t of rootsInUnit(...coeffs, scale)) {\n\t\tconst point = segmentPoint(seg, t);\n\t\tconst s = ((point[0] - p[0]) * dx + (point[1] - p[1]) * dy) / len2;\n\t\tif (s >= -1e-9 && s <= 1 + 1e-9) out.push({ s: Math.min(1, Math.max(0, s)), t, point });\n\t}\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Knife\n// ---------------------------------------------------------------------------\n\ninterface Hit {\n\t/** Knife position: segment index + parameter. */\n\ts: number;\n\tloop: number;\n\tseg: number;\n\tt: number;\n\tpoint: Point;\n}\n\nconst near = (a: Point, b: Point, eps: number) => Math.abs(a[0] - b[0]) <= eps && Math.abs(a[1] - b[1]) <= eps;\nconst degenerate = (seg: readonly Point[], eps: number) => seg.every((p) => near(p, seg[0], eps));\n\n/** The loop's pieces from (seg a, t a) forward to (seg b, t b); `full` goes all the way round when they coincide. */\nfunction arc(l: Loop, a: { seg: number; t: number }, b: { seg: number; t: number }, eps: number): { segs: Point[][]; chord: boolean[] } {\n\tconst n = l.segs.length;\n\tconst segs: Point[][] = [];\n\tconst chord: boolean[] = [];\n\tconst push = (k: number, t0: number, t1: number) => {\n\t\tconst piece = t0 <= 0 && t1 >= 1 ? l.segs[k].slice() : subSegment(l.segs[k], t0, t1);\n\t\tif (!degenerate(piece, eps)) {\n\t\t\tsegs.push(piece);\n\t\t\tchord.push(l.chord[k]);\n\t\t}\n\t};\n\tif (a.seg === b.seg && b.t >= a.t + 1e-12) {\n\t\tpush(a.seg, a.t, b.t);\n\t\treturn { segs, chord };\n\t}\n\tpush(a.seg, a.t, 1);\n\tfor (let k = (a.seg + 1) % n; k !== b.seg; k = (k + 1) % n) push(k, 0, 1);\n\tpush(b.seg, 0, b.t);\n\treturn { segs, chord };\n}\n\n/** Line segments through `points`. */\nfunction polySegs(points: readonly Point[], eps: number): Point[][] {\n\tconst out: Point[][] = [];\n\tfor (let i = 1; i < points.length; i++) if (!near(points[i - 1], points[i], eps)) out.push([points[i - 1], points[i]]);\n\treturn out;\n}\n\n/**\n * Cuts the filled path along the open polyline `knife`. Returns one path per resulting face\n * (an outline with its holes), or null when the knife crossed nothing from edge to edge. A\n * stretch of the knife that starts or ends inside the shape does not cut.\n */\nexport function knifePath(path: BezierPathInput, fillRule: FillRule, knife: readonly Point[]): BezierPath[] | null {\n\tconst pts = knife.filter((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]));\n\tif (pts.length < 2) return null;\n\tlet faces = facesOf(path, fillRule);\n\tif (!faces.length) return null;\n\tconst b = bezierPathBounds(path);\n\tconst size = b ? Math.max(b.maxX - b.minX, b.maxY - b.minY, 1) : 1;\n\tconst eps = size * 1e-9;\n\tlet cursor = -Infinity;\n\tlet cuts = 0;\n\tfor (let guard = 0; guard < 512; guard++) {\n\t\tlet best: { face: number; from: Hit; to: Hit } | null = null;\n\t\tfaces.forEach((face, fi) => {\n\t\t\tconst hits: Hit[] = [];\n\t\t\tfor (let m = 0; m < pts.length - 1; m++) {\n\t\t\t\tif (m + 1 < cursor - 1e-9) continue;\n\t\t\t\tconst kb = { minX: Math.min(pts[m][0], pts[m + 1][0]), maxX: Math.max(pts[m][0], pts[m + 1][0]), minY: Math.min(pts[m][1], pts[m + 1][1]), maxY: Math.max(pts[m][1], pts[m + 1][1]) };\n\t\t\t\tface.loops.forEach((l, li) =>\n\t\t\t\t\tl.segs.forEach((seg, si) => {\n\t\t\t\t\t\tconst sb = segmentBounds(seg);\n\t\t\t\t\t\tif (sb.maxX < kb.minX - eps || sb.minX > kb.maxX + eps || sb.maxY < kb.minY - eps || sb.minY > kb.maxY + eps) return;\n\t\t\t\t\t\tfor (const h of lineHits(pts[m], pts[m + 1], seg)) {\n\t\t\t\t\t\t\tconst s = m + h.s;\n\t\t\t\t\t\t\tif (s >= cursor - 1e-9) hits.push({ s, loop: li, seg: si, t: h.t, point: h.point });\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t}\n\t\t\thits.sort((x, y) => x.s - y.s);\n\t\t\tconst unique = hits.filter((h, i) => i === 0 || h.s - hits[i - 1].s > 1e-9 || !near(h.point, hits[i - 1].point, eps * 10));\n\t\t\tconst inside = (s: number) => {\n\t\t\t\tconst m = Math.min(pts.length - 2, Math.floor(s));\n\t\t\t\tconst u = s - m;\n\t\t\t\tconst q: Point = [pts[m][0] + (pts[m + 1][0] - pts[m][0]) * u, pts[m][1] + (pts[m + 1][1] - pts[m][1]) * u];\n\t\t\t\treturn windingNumber(face.loops.map((l) => l.segs), q) !== 0;\n\t\t\t};\n\t\t\tfor (let i = 0; i < unique.length - 1; i++) {\n\t\t\t\tconst from = unique[i];\n\t\t\t\tconst to = unique[i + 1];\n\t\t\t\tif (near(from.point, to.point, eps * 10) || !inside((from.s + to.s) / 2)) continue;\n\t\t\t\tif (!best || from.s < best.from.s) best = { face: fi, from, to };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t});\n\t\tif (!best) break;\n\t\tconst { face: fi, from, to } = best as { face: number; from: Hit; to: Hit };\n\t\tconst face = faces[fi];\n\t\t// The chord: the crossing points with the knife's corners between them.\n\t\tconst corners: Point[] = [from.point];\n\t\tfor (let m = Math.floor(from.s) + 1; m <= to.s - 1e-12 && m < pts.length; m++) if (m > from.s) corners.push(pts[m]);\n\t\tcorners.push(to.point);\n\t\tconst forward = polySegs(corners, eps);\n\t\tconst back = polySegs(corners.slice().reverse(), eps);\n\t\tconst la = face.loops[from.loop];\n\t\tconst loops = face.loops.filter((_, i) => i !== from.loop && i !== to.loop);\n\t\tif (from.loop === to.loop) {\n\t\t\tconst ab = arc(la, from, to, eps);\n\t\t\tconst ba = arc(la, to, from, eps);\n\t\t\tloops.push(\n\t\t\t\t{ segs: [...ab.segs, ...back], chord: [...ab.chord, ...back.map(() => true)] },\n\t\t\t\t{ segs: [...ba.segs, ...forward], chord: [...ba.chord, ...forward.map(() => true)] }\n\t\t\t);\n\t\t} else {\n\t\t\t// The chord joins two loops into one, with the chord on both sides of a zero-width slit.\n\t\t\tconst lb = face.loops[to.loop];\n\t\t\tconst roundA = arc(la, from, from, eps);\n\t\t\tconst roundB = arc(lb, to, to, eps);\n\t\t\tloops.push({\n\t\t\t\tsegs: [...roundA.segs, ...forward, ...roundB.segs, ...back],\n\t\t\t\tchord: [...roundA.chord, ...forward.map(() => true), ...roundB.chord, ...back.map(() => true)]\n\t\t\t});\n\t\t}\n\t\tfaces = [...faces.slice(0, fi), ...groupFaces(loops), ...faces.slice(fi + 1)];\n\t\tcursor = to.s;\n\t\tcuts++;\n\t}\n\treturn cuts ? faces.map(faceToPath) : null;\n}\n\n// ---------------------------------------------------------------------------\n// Eraser\n// ---------------------------------------------------------------------------\n\n/** The area a round eraser of diameter `width` covers along `points` (a disc for one point). */\nexport function eraserRegion(points: readonly Point[], width: number): BezierPath {\n\tconst pts = points.filter((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]));\n\tconst r = width / 2;\n\tif (!pts.length || !(r > 0)) return [];\n\tconst moved = pts.some((p) => !near(p, pts[0], 1e-9));\n\tif (!moved) {\n\t\tconst [cx, cy] = pts[0];\n\t\tconst k = r * 0.5522847498307936;\n\t\treturn [\n\t\t\t{ type: 'M', x: cx, y: cy - r },\n\t\t\t{ type: 'C', x1: cx + k, y1: cy - r, x2: cx + r, y2: cy - k, x: cx + r, y: cy },\n\t\t\t{ type: 'C', x1: cx + r, y1: cy + k, x2: cx + k, y2: cy + r, x: cx, y: cy + r },\n\t\t\t{ type: 'C', x1: cx - k, y1: cy + r, x2: cx - r, y2: cy + k, x: cx - r, y: cy },\n\t\t\t{ type: 'C', x1: cx - r, y1: cy - k, x2: cx - k, y2: cy - r, x: cx, y: cy - r },\n\t\t\t{ type: 'Z' }\n\t\t];\n\t}\n\tconst line: BezierPath = [{ type: 'M', x: pts[0][0], y: pts[0][1] }, ...pts.slice(1).map((p) => ({ type: 'L' as const, x: p[0], y: p[1] }))];\n\treturn outlineStroke(line, { width, cap: 'round', join: 'round' });\n}\n\n/**\n * A filled path with `region` removed: one path per remaining face, or null when the region\n * does not overlap the fill.\n */\nexport function eraseFromFill(path: BezierPathInput, fillRule: FillRule, region: BezierPathInput): BezierPath[] | null {\n\tconst overlap = pathBoolean('intersection', [{ path, fillRule }, { path: region }]).path;\n\tif (!pathToContours(overlap).some((c) => Math.abs(contourSignedArea(closeContour(c, true))) > 1e-9)) return null;\n\tconst rest = pathBoolean('difference', [{ path, fillRule }, { path: region }]).path;\n\treturn splitIntoFaces(rest);\n}\n\n/**\n * The parts of a subpath's outline outside `region`, as open subpaths that keep the original\n * curves exactly; null when the outline does not enter the region. A closed subpath's last\n * and first parts join at its first anchor. With `keep: 'inside'` the parts inside the region are\n * kept instead (null when the outline never leaves it), as a clipping mask clips an open path.\n */\nexport function eraseFromOutline(sp: VectorSubpath, region: BezierPathInput, options: { keep?: 'outside' | 'inside'; fillRule?: FillRule } = {}): VectorSubpath[] | null {\n\tconst regionSegs = pathToContours(region).flatMap((c) => closeContour(c, true));\n\tif (!regionSegs.length) return null;\n\tconst rb = bezierPathBounds(region);\n\tconst count = subpathSegmentCount(sp);\n\tif (!rb || count === 0) return null;\n\tconst inside = options.keep === 'inside';\n\tconst removed = (p: Point) => pointInBezierPath(region, p, options.fillRule ?? 'nonzero') !== inside;\n\ttype Piece = { seg: number; t0: number; t1: number };\n\tconst runs: Piece[][] = [];\n\tlet cur: Piece[] = [];\n\tlet any = false;\n\tfor (let i = 0; i < count; i++) {\n\t\tconst seg = subpathSegment(sp, i);\n\t\tconst sb = segmentBounds(seg);\n\t\tconst ts = [0, 1];\n\t\tif (!(sb.maxX < rb.minX || sb.minX > rb.maxX || sb.maxY < rb.minY || sb.minY > rb.maxY)) {\n\t\t\tfor (const r of regionSegs) {\n\t\t\t\tconst b = segmentBounds(r);\n\t\t\t\tif (b.maxX < sb.minX || b.minX > sb.maxX || b.maxY < sb.minY || b.minY > sb.maxY) continue;\n\t\t\t\tfor (const hit of segmentIntersections(seg, r)) if (hit.ta > 1e-9 && hit.ta < 1 - 1e-9) ts.push(hit.ta);\n\t\t\t}\n\t\t}\n\t\tts.sort((a, b) => a - b);\n\t\tfor (let k = 0; k < ts.length - 1; k++) {\n\t\t\tconst t0 = ts[k];\n\t\t\tconst t1 = ts[k + 1];\n\t\t\tif (t1 - t0 < 1e-9) continue;\n\t\t\tif (removed(segmentPoint(seg, (t0 + t1) / 2))) {\n\t\t\t\tany = true;\n\t\t\t\tif (cur.length) runs.push(cur);\n\t\t\t\tcur = [];\n\t\t\t} else cur.push({ seg: i, t0, t1 });\n\t\t}\n\t}\n\tif (cur.length) runs.push(cur);\n\tif (!any) return null;\n\t// A closed path's last and first runs meet at anchor 0.\n\tif (sp.closed && runs.length >= 2) {\n\t\tconst first = runs[0][0];\n\t\tconst lastRun = runs[runs.length - 1];\n\t\tconst last = lastRun[lastRun.length - 1];\n\t\tif (first.seg === 0 && first.t0 === 0 && last.seg === count - 1 && last.t1 === 1) lastRun.push(...runs.shift()!);\n\t}\n\tconst out: VectorSubpath[] = [];\n\tfor (const run of runs) {\n\t\tconst anchors: VectorAnchor[] = [];\n\t\tfor (const { seg, t0, t1 } of run) {\n\t\t\tconst c = subSegment(subpathSegment(sp, seg), t0, t1);\n\t\t\tconst line = c.length === 2;\n\t\t\tconst start = c[0];\n\t\t\tconst end = c[c.length - 1];\n\t\t\tif (!anchors.length) anchors.push({ x: start[0], y: start[1] });\n\t\t\tif (!line) anchors[anchors.length - 1].out = { x: c[1][0], y: c[1][1] };\n\t\t\tconst next: VectorAnchor = { x: end[0], y: end[1] };\n\t\t\tif (!line) next.in = { x: c[2][0], y: c[2][1] };\n\t\t\tanchors.push(next);\n\t\t}\n\t\tif (anchors.length < 2) continue;\n\t\tfor (const a of anchors) {\n\t\t\tconst type = inferAnchorType(a);\n\t\t\tif (type !== 'corner') a.type = type;\n\t\t}\n\t\tout.push({ anchors, closed: false });\n\t}\n\treturn out;\n}\n\n/** Bounds of a point list (for picking the paths a knife or eraser stroke can reach). */\nexport function polylineBounds(points: readonly Point[], pad = 0): { x: number; y: number; width: number; height: number } | null {\n\tif (!points.length) return null;\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const [x, y] of points) {\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\treturn { x: minX - pad, y: minY - pad, width: maxX - minX + 2 * pad, height: maxY - minY + 2 * pad };\n}\n\n/** Ramer–Douglas–Peucker reduction of a polyline (keeps both ends). */\nexport function simplifyPolyline(points: readonly Point[], tolerance: number): Point[] {\n\tconst n = points.length;\n\tif (n < 3) return points.slice();\n\tconst keep = new Array<boolean>(n).fill(false);\n\tkeep[0] = keep[n - 1] = true;\n\tconst stack: [number, number][] = [[0, n - 1]];\n\twhile (stack.length) {\n\t\tconst [a, b] = stack.pop()!;\n\t\tconst [ax, ay] = points[a];\n\t\tconst dx = points[b][0] - ax;\n\t\tconst dy = points[b][1] - ay;\n\t\tconst len = Math.hypot(dx, dy);\n\t\tlet far = -1;\n\t\tlet farD = 0;\n\t\tfor (let i = a + 1; i < b; i++) {\n\t\t\tconst [px, py] = points[i];\n\t\t\tconst d = len < 1e-12 ? Math.hypot(px - ax, py - ay) : Math.abs((px - ax) * dy - (py - ay) * dx) / len;\n\t\t\tif (d > farD) {\n\t\t\t\tfarD = d;\n\t\t\t\tfar = i;\n\t\t\t}\n\t\t}\n\t\tif (far >= 0 && farD > tolerance) {\n\t\t\tkeep[far] = true;\n\t\t\tstack.push([a, far], [far, b]);\n\t\t}\n\t}\n\treturn points.filter((_, i) => keep[i]);\n}\n\n","/**\n * Visual filters of vector nodes: drop shadow, inner shadow, outer and inner glow, layer blur and\n * feather, drawn as one SVG `<filter>` per node (in the canvases and in SVG export alike).\n *\n * The filter takes everything the node draws (`SourceGraphic`) and its silhouette\n * (`SourceAlpha`) in the node's own space, and builds, in this order:\n *\n * 1. Inner shadows and inner glows, in stack order, over the body: the inverse of the silhouette\n * (offset for a shadow), grown by `spread` (which chokes the shadow inwards), blurred, coloured\n * and clipped to the silhouette.\n * 2. Feather: the body kept where the silhouette, eroded by half the radius and blurred by a\n * quarter of it, is, so the edge fades from about nothing at the outline to full strength\n * `radius` inside.\n * 3. Drop shadows and outer glows, in stack order, under the body: the silhouette grown by\n * `spread`, blurred, offset (shadows) and coloured.\n * 4. Layer blurs, over the whole result (shadows included).\n *\n * Blur radii are CSS / Figma blur radii: the Gaussian's standard deviation is half the radius, and\n * it reaches about 1.5 radii. Background blur needs the backdrop, which an SVG filter cannot read\n * (`BackgroundImage` was dropped from browsers) and which other SVG readers cannot either, so it is\n * left to the planned lens objects.\n *\n * The construction of feather, glows and inner shadows from blur, offset and clip follows\n * designcraft `crates/render/src/fx.rs` (`draw_item_fx`, `inner_edge`, `split`); the effect set,\n * defaults and reach follow figcraft `crates/doc/src/props.rs` (`EffectKind`, `Shadow`,\n * `Effect::reach`). Ported from ArtCraft (MIT OR Apache-2.0, © ArtCraft Team and contributors; see\n * THIRD_PARTY_NOTICES.md).\n */\nimport type { VectorEffectParamSpec } from './appearance.js';\nimport { VECTOR_FILTER_TYPES, type VectorBounds, type VectorFilter, type VectorFilterParamsMap, type VectorFilterType, type VectorNode } from './types.js';\n\n/** Default parameters of each filter type. */\nexport const VECTOR_FILTER_DEFAULTS: { readonly [K in VectorFilterType]: VectorFilterParamsMap[K] } = {\n\tdropShadow: { color: '#000000', opacity: 0.25, offsetX: 0, offsetY: 4, blur: 4, spread: 0 },\n\tinnerShadow: { color: '#000000', opacity: 0.25, offsetX: 0, offsetY: 4, blur: 4, spread: 0 },\n\touterGlow: { color: '#ffffbe', opacity: 0.75, blur: 10, spread: 0 },\n\tinnerGlow: { color: '#ffffbe', opacity: 0.75, blur: 10, spread: 0 },\n\tlayerBlur: { radius: 4 },\n\tfeather: { radius: 8 }\n};\n\nexport const VECTOR_FILTER_LABELS: Readonly<Record<VectorFilterType, string>> = {\n\tdropShadow: 'Drop Shadow',\n\tinnerShadow: 'Inner Shadow',\n\touterGlow: 'Outer Glow',\n\tinnerGlow: 'Inner Glow',\n\tlayerBlur: 'Layer Blur',\n\tfeather: 'Feather'\n};\n\n/** How a panel edits one filter parameter: an effect parameter spec, or a colour. */\nexport type VectorFilterParamSpec = VectorEffectParamSpec | { key: string; label: string; kind: 'color' };\n\nconst SHADOW_SPECS: VectorFilterParamSpec[] = [\n\t{ key: 'color', label: 'Color', kind: 'color' },\n\t{ key: 'opacity', label: 'Opacity', kind: 'number', min: 0, max: 1, step: 0.01, slider: true },\n\t{ key: 'offsetX', label: 'X', kind: 'number', min: -1000, max: 1000, step: 1 },\n\t{ key: 'offsetY', label: 'Y', kind: 'number', min: -1000, max: 1000, step: 1 },\n\t{ key: 'blur', label: 'Blur', kind: 'number', min: 0, max: 500, step: 1 },\n\t{ key: 'spread', label: 'Spread', kind: 'number', min: 0, max: 500, step: 1 }\n];\nconst GLOW_SPECS = SHADOW_SPECS.filter((s) => s.key !== 'offsetX' && s.key !== 'offsetY');\n\n/** The editable parameters of each filter type, in panel order. */\nexport const VECTOR_FILTER_PARAM_SPECS: Readonly<Record<VectorFilterType, readonly VectorFilterParamSpec[]>> = {\n\tdropShadow: SHADOW_SPECS,\n\tinnerShadow: SHADOW_SPECS.map((s) => (s.key === 'spread' ? { ...s, label: 'Choke' } : s)),\n\touterGlow: GLOW_SPECS,\n\tinnerGlow: GLOW_SPECS.map((s) => (s.key === 'spread' ? { ...s, label: 'Choke' } : s)),\n\tlayerBlur: [{ key: 'radius', label: 'Radius', kind: 'number', min: 0, max: 500, step: 1, slider: true }],\n\tfeather: [{ key: 'radius', label: 'Radius', kind: 'number', min: 0, max: 500, step: 1, slider: true }]\n};\n\nexport function isVectorFilterType(value: unknown): value is VectorFilterType {\n\treturn typeof value === 'string' && (VECTOR_FILTER_TYPES as readonly string[]).includes(value);\n}\n\n/**\n * Filter parameters with defaults (or `base`) filled in; values of the wrong type are dropped,\n * numbers clamped to their range, unknown keys ignored. Null for an unknown type.\n */\nexport function normalizeVectorFilterParams<K extends VectorFilterType>(type: K, params: Record<string, unknown> | null | undefined, base?: VectorFilterParamsMap[K]): VectorFilterParamsMap[K] | null {\n\tif (!isVectorFilterType(type)) return null;\n\tconst out: Record<string, unknown> = { ...VECTOR_FILTER_DEFAULTS[type], ...((base as Record<string, unknown> | undefined) ?? {}) };\n\tfor (const spec of VECTOR_FILTER_PARAM_SPECS[type]) {\n\t\tconst v = params?.[spec.key];\n\t\tif (v === undefined) continue;\n\t\tif (spec.kind === 'number' && typeof v === 'number' && Number.isFinite(v)) out[spec.key] = Math.min(spec.max ?? Infinity, Math.max(spec.min ?? -Infinity, v));\n\t\telse if (spec.kind === 'color' && typeof v === 'string' && v.trim()) out[spec.key] = v.trim();\n\t}\n\treturn out as VectorFilterParamsMap[K];\n}\n\n/** The parameter keys a filter type takes (for error messages). */\nexport function vectorFilterParamKeys(type: VectorFilterType): string[] {\n\treturn VECTOR_FILTER_PARAM_SPECS[type].map((s) => s.key);\n}\n\n/** A new filter of `type` (defaults merged with `params`), or null for an unknown type. */\nexport function createVectorFilter<K extends VectorFilterType>(type: K, id: string, params?: Partial<VectorFilterParamsMap[K]> | Record<string, unknown>): Extract<VectorFilter, { type: K }> | null {\n\tconst p = normalizeVectorFilterParams(type, params as Record<string, unknown>);\n\treturn p ? ({ id, type, params: p } as unknown as Extract<VectorFilter, { type: K }>) : null;\n}\n\n/**\n * Reads filters from untrusted data (JSON, agent input, SVG): unknown types and malformed entries\n * are dropped, parameters normalized, ids made unique.\n */\nexport function sanitizeVectorFilters(value: unknown): VectorFilter[] {\n\tif (!Array.isArray(value)) return [];\n\tconst out: VectorFilter[] = [];\n\tconst used = new Set<string>();\n\tfor (const raw of value) {\n\t\tif (!raw || typeof raw !== 'object') continue;\n\t\tconst r = raw as Record<string, unknown>;\n\t\tif (!isVectorFilterType(r.type)) continue;\n\t\tlet id = typeof r.id === 'string' && r.id && !used.has(r.id) ? r.id : '';\n\t\tfor (let k = out.length + 1; !id; k++) if (!used.has(`filter${k}`)) id = `filter${k}`;\n\t\tused.add(id);\n\t\tconst filter = createVectorFilter(r.type, id, (r.params as Record<string, unknown>) ?? {})!;\n\t\tout.push(r.enabled === false ? { ...filter, enabled: false } : filter);\n\t}\n\treturn out;\n}\n\n/** The node's filters that draw something (enabled, known, with a visible result). */\nexport function activeVectorFilters(node: Pick<VectorNode, 'filters'>): VectorFilter[] {\n\treturn (node.filters ?? []).filter((f) => {\n\t\tif (f.enabled === false || !isVectorFilterType(f.type)) return false;\n\t\tif (f.type === 'layerBlur' || f.type === 'feather') return f.params.radius > 0;\n\t\treturn f.params.opacity > 0;\n\t});\n}\n\n/** How far a node's filters can paint past what it draws, in its own units. */\nexport function vectorFilterReach(filters: readonly VectorFilter[]): number {\n\tlet outer = 0;\n\tlet blur = 0;\n\tfor (const f of filters) {\n\t\tswitch (f.type) {\n\t\t\tcase 'dropShadow':\n\t\t\tcase 'innerShadow':\n\t\t\t\touter = Math.max(outer, Math.max(Math.abs(f.params.offsetX), Math.abs(f.params.offsetY)) + f.params.blur * 1.5 + f.params.spread);\n\t\t\t\tbreak;\n\t\t\tcase 'outerGlow':\n\t\t\tcase 'innerGlow':\n\t\t\t\touter = Math.max(outer, f.params.blur * 1.5 + f.params.spread);\n\t\t\t\tbreak;\n\t\t\tcase 'layerBlur':\n\t\t\t\tblur += f.params.radius * 1.5;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn outer + blur;\n}\n\n/** One SVG filter primitive as data: a tag (`feGaussianBlur`, …), its attributes and children. */\nexport interface VectorFilterPrimitive {\n\ttag: string;\n\tattrs: Readonly<Record<string, string | number>>;\n\tchildren?: VectorFilterPrimitive[];\n}\n\n/**\n * A node's filter as data: draw `<filter id filterUnits=\"userSpaceOnUse\" x y width height\n * color-interpolation-filters=\"sRGB\">` with the primitives inside, and reference it with\n * `filter=\"url(#id)\"` on the node's element. The region is in the node's own space.\n */\nexport interface VectorRenderFilter {\n\tid: string;\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\tprimitives: VectorFilterPrimitive[];\n}\n\nconst round = (n: number) => Math.round(n * 1000) / 1000;\nconst fe = (tag: string, attrs: Record<string, string | number>, children?: VectorFilterPrimitive[]): VectorFilterPrimitive => ({ tag, attrs, ...(children ? { children } : {}) });\n\n/**\n * The SVG filter for `filters` over a node whose drawing covers `bounds` (its own space, strokes\n * included), or null when none of them draws.\n */\nexport function vectorFilterSvg(filters: readonly VectorFilter[], id: string, bounds: VectorBounds | null): VectorRenderFilter | null {\n\tconst active = filters.filter((f) => activeVectorFilters({ filters: [f] }).length);\n\tif (!active.length || !bounds) return null;\n\tconst primitives: VectorFilterPrimitive[] = [];\n\tlet body = 'SourceGraphic';\n\tlet n = 0;\n\tconst name = (part: string) => `${part}${n}`;\n\t// Inner shadows and glows, over the body and clipped to the silhouette.\n\tfor (const f of active) {\n\t\tif (f.type !== 'innerShadow' && f.type !== 'innerGlow') continue;\n\t\tn++;\n\t\tconst p = f.params;\n\t\tprimitives.push(fe('feComponentTransfer', { in: 'SourceAlpha', result: name('inv') }, [fe('feFuncA', { type: 'table', tableValues: '1 0' })]));\n\t\tlet last = name('inv');\n\t\tif (f.type === 'innerShadow' && (f.params.offsetX || f.params.offsetY)) {\n\t\t\tprimitives.push(fe('feOffset', { in: last, dx: round(f.params.offsetX), dy: round(f.params.offsetY), result: name('off') }));\n\t\t\tlast = name('off');\n\t\t}\n\t\tif (p.spread > 0) {\n\t\t\tprimitives.push(fe('feMorphology', { in: last, operator: 'dilate', radius: round(p.spread), result: name('grow') }));\n\t\t\tlast = name('grow');\n\t\t}\n\t\tif (p.blur > 0) {\n\t\t\tprimitives.push(fe('feGaussianBlur', { in: last, stdDeviation: round(p.blur / 2), result: name('blur') }));\n\t\t\tlast = name('blur');\n\t\t}\n\t\tprimitives.push(fe('feFlood', { 'flood-color': p.color, 'flood-opacity': round(p.opacity), result: name('color') }));\n\t\tprimitives.push(fe('feComposite', { in: name('color'), in2: last, operator: 'in', result: name('tint') }));\n\t\tprimitives.push(fe('feComposite', { in: name('tint'), in2: 'SourceAlpha', operator: 'in', result: name('inner') }));\n\t\tprimitives.push(fe('feComposite', { in: name('inner'), in2: body, operator: 'over', result: name('body') }));\n\t\tbody = name('body');\n\t}\n\t// Feather: keep the body where the eroded, blurred silhouette is.\n\tfor (const f of active) {\n\t\tif (f.type !== 'feather') continue;\n\t\tn++;\n\t\tconst r = f.params.radius;\n\t\tprimitives.push(fe('feMorphology', { in: 'SourceAlpha', operator: 'erode', radius: round(r / 2), result: name('core') }));\n\t\tprimitives.push(fe('feGaussianBlur', { in: name('core'), stdDeviation: round(r / 4), result: name('fade') }));\n\t\tprimitives.push(fe('feComposite', { in: body, in2: name('fade'), operator: 'in', result: name('body') }));\n\t\tbody = name('body');\n\t}\n\t// Drop shadows and outer glows, under the body.\n\tconst under: string[] = [];\n\tfor (const f of active) {\n\t\tif (f.type !== 'dropShadow' && f.type !== 'outerGlow') continue;\n\t\tn++;\n\t\tconst p = f.params;\n\t\tlet last = 'SourceAlpha';\n\t\tif (p.spread > 0) {\n\t\t\tprimitives.push(fe('feMorphology', { in: last, operator: 'dilate', radius: round(p.spread), result: name('grow') }));\n\t\t\tlast = name('grow');\n\t\t}\n\t\tif (p.blur > 0) {\n\t\t\tprimitives.push(fe('feGaussianBlur', { in: last, stdDeviation: round(p.blur / 2), result: name('blur') }));\n\t\t\tlast = name('blur');\n\t\t}\n\t\tif (f.type === 'dropShadow' && (f.params.offsetX || f.params.offsetY)) {\n\t\t\tprimitives.push(fe('feOffset', { in: last, dx: round(f.params.offsetX), dy: round(f.params.offsetY), result: name('off') }));\n\t\t\tlast = name('off');\n\t\t}\n\t\tprimitives.push(fe('feFlood', { 'flood-color': p.color, 'flood-opacity': round(p.opacity), result: name('color') }));\n\t\tprimitives.push(fe('feComposite', { in: name('color'), in2: last, operator: 'in', result: name('shadow') }));\n\t\tunder.push(name('shadow'));\n\t}\n\tif (under.length) {\n\t\tn++;\n\t\tprimitives.push(fe('feMerge', { result: name('merged') }, [...under, body].map((i) => fe('feMergeNode', { in: i }))));\n\t\tbody = name('merged');\n\t}\n\t// Layer blurs, over everything.\n\tfor (const f of active) {\n\t\tif (f.type !== 'layerBlur') continue;\n\t\tn++;\n\t\tprimitives.push(fe('feGaussianBlur', { in: body, stdDeviation: round(f.params.radius / 2), result: name('body') }));\n\t\tbody = name('body');\n\t}\n\t// The last primitive is the filter's result; make sure it is the body.\n\tconst lastResult = primitives[primitives.length - 1]?.attrs.result;\n\tif (lastResult !== body) primitives.push(fe('feMerge', {}, [fe('feMergeNode', { in: body })]));\n\tconst pad = vectorFilterReach(active) + 2;\n\treturn {\n\t\tid,\n\t\tx: round(bounds.x - pad),\n\t\ty: round(bounds.y - pad),\n\t\twidth: round(bounds.width + 2 * pad),\n\t\theight: round(bounds.height + 2 * pad),\n\t\tprimitives\n\t};\n}\n\nconst escapeAttr = (value: string) => value.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n\nfunction primitiveMarkup(p: VectorFilterPrimitive): string {\n\tconst attrs = Object.entries(p.attrs)\n\t\t.map(([k, v]) => ` ${k}=\"${escapeAttr(String(v))}\"`)\n\t\t.join('');\n\treturn p.children?.length ? `<${p.tag}${attrs}>${p.children.map(primitiveMarkup).join('')}</${p.tag}>` : `<${p.tag}${attrs}/>`;\n}\n\n/** A render filter as SVG markup (`extra` is added to the `<filter>` element's attributes). */\nexport function vectorFilterMarkup(filter: VectorRenderFilter, extra = ''): string {\n\treturn `<filter id=\"${escapeAttr(filter.id)}\" filterUnits=\"userSpaceOnUse\" x=\"${filter.x}\" y=\"${filter.y}\" width=\"${filter.width}\" height=\"${filter.height}\" color-interpolation-filters=\"sRGB\"${extra}>${filter.primitives.map(primitiveMarkup).join('')}</filter>`;\n}\n\n/** Labels of the filter types, for menus. */\nexport const VECTOR_FILTER_MENU: readonly { type: VectorFilterType; label: string }[] = VECTOR_FILTER_TYPES.map((type) => ({ type, label: VECTOR_FILTER_LABELS[type] }));\n","/**\n * Masks and clipping. A group with `mask` set is a mask group: its first (bottom) child is the\n * mask and the children above it (the contents) show only where the mask lets them.\n *\n * - `clip`: a clipping mask, by the mask's outline (every path inside it, filled by its own fill\n * rule, whatever its paint; the mask's paint is not drawn). Drawn as an SVG `<clipPath>`.\n * - `alpha` / `luminance`: an opacity mask, by the mask's drawn alpha or its brightness (times its\n * alpha). Drawn as an SVG `<mask>` holding the mask as drawn (`mask-type=\"alpha\"` for alpha).\n * - `invert` swaps what shows and what hides. An inverted clip is the even-odd clip of a large\n * rectangle with the mask's outline (its boolean union) cut out of it, as pdfcraft's redaction\n * clips content out of a region; an inverted opacity mask draws the mask through a colour\n * matrix filter that turns its alpha (or brightness) upside down.\n *\n * Geometry uses the masked result: the contents' shapes clipped by the mask's region (its outline\n * for clips, its painted area for opacity masks) with our booleans, open paths cut where they\n * cross it. Bounds, hit-testing, snapping, outlines, booleans and the Pathfinder all read it\n * through `vectorAppearanceShapes`. An opacity mask is hit and bounded by where it paints at all,\n * not by how strongly: a dark luminance mask still counts.\n *\n * A hidden mask child turns the mask off and the contents draw whole (figcraft\n * `crates/doc/src/geometry.rs`: a hidden mask doesn't clip). The group's own opacity, blend mode\n * and filters apply after masking (its drop shadow follows the masked silhouette), and its path\n * effects act on the contents only, not on the mask.\n *\n * Extension point for lens objects: a lens is a shape that changes what is beneath it (blur,\n * magnify, recolour). It will reuse this module's pieces rather than the mask group model: the\n * region of a shape (`vectorMaskRegionShapes` with the lens as the source) clips a filtered copy\n * of the backdrop the way `vectorMaskRenderData` clips the contents, and hit-testing and bounds\n * stay those of the lens shape itself. See `VectorMaskRenderData` for where a renderer draws it.\n *\n * The mask model (`is_mask`, `MaskType`: alpha, vector, luminance; hidden masks off) follows\n * figcraft `crates/doc/src/node.rs` and `geometry.rs`; clipping a node's drawing to a shape layer\n * by layer follows designcraft `crates/render/src/fx.rs` (`push_clip_layer` around each body); the\n * inverted clip follows pdfcraft `crates/redact/src/interp.rs` (`clip_out`). Ported from ArtCraft\n * (MIT OR Apache-2.0, © ArtCraft Team and contributors; see THIRD_PARTY_NOTICES.md).\n */\nimport { bezierPathBounds, bezierPathToSvg, distanceToBezierPath, transformBezierPath, pointInBezierPath, type AffineMatrix, type BezierPath } from '../canvas/pathops/bezier.js';\nimport { pathBoolean } from '../canvas/pathops/boolean.js';\nimport { IDENTITY_MATRIX, isIdentityMatrix, multiplyMatrices } from '../geom/affine.js';\nimport { applyVectorEffects, expandVectorAppearance, vectorAppearanceNodes, vectorAppearanceShapes, vectorChildAppearanceShapes, type VectorAppearanceShape } from './appearance.js';\nimport { bezierToSubpaths, hitTestVectorNode, nodeMatrix, subpathsToBezier, unionBounds, vectorNodeBounds } from './geometry.js';\nimport { vectorPaintLayerNodes, vectorStrokeStackReach } from './paintLayers.js';\nimport { eraseFromOutline } from './pathCut.js';\nimport { vectorStrokeOutlinePath } from './stroke.js';\nimport { activeVectorFilters, vectorFilterReach, type VectorFilterPrimitive, type VectorRenderFilter } from './filters.js';\nimport { VECTOR_MASK_TYPES, type VectorBounds, type VectorFillRule, type VectorGroupNode, type VectorMask, type VectorMaskType, type VectorNode, type Vec2 } from './types.js';\n\n/** A group whose `mask` takes effect (not a blend or live boolean, with a child to be the mask). */\nexport type VectorMaskGroup = VectorGroupNode & { mask: VectorMask };\n\n/** Whether `value` is a mask type. */\nexport function isVectorMaskType(value: unknown): value is VectorMaskType {\n\treturn typeof value === 'string' && (VECTOR_MASK_TYPES as readonly string[]).includes(value);\n}\n\n/** Reads mask settings from untrusted data (JSON, agent input, SVG data attributes); null when invalid. */\nexport function sanitizeVectorMask(value: unknown): VectorMask | null {\n\tif (!value || typeof value !== 'object') return null;\n\tconst r = value as Record<string, unknown>;\n\tif (!isVectorMaskType(r.type)) return null;\n\treturn r.invert === true ? { type: r.type, invert: true } : { type: r.type };\n}\n\n/** Whether a node is a mask group: a group with `mask`, at least one child, and not a blend or live boolean. */\nexport function isVectorMaskGroup(node: VectorNode | null | undefined): node is VectorMaskGroup {\n\treturn node?.type === 'group' && !!node.mask && isVectorMaskType(node.mask.type) && !node.blend && !node.liveBoolean && node.children.length > 0;\n}\n\n/** Whether a mask group's mask is on: its mask child is visible. */\nexport function isVectorMaskActive(node: VectorNode | null | undefined): node is VectorMaskGroup {\n\treturn isVectorMaskGroup(node) && node.children[0].visible !== false;\n}\n\n/** A mask group's mask (its first child), or null. */\nexport function vectorMaskSource(node: VectorNode | null | undefined): VectorNode | null {\n\treturn isVectorMaskGroup(node) ? node.children[0] : null;\n}\n\n/** The children a mask group masks (all but the first); a plain group's children. */\nexport function vectorMaskContents(group: VectorGroupNode): VectorNode[] {\n\treturn isVectorMaskGroup(group) ? group.children.slice(1) : group.children;\n}\n\n/** The mask group whose mask is `id`, or null. */\nexport function vectorMaskOwner(nodes: readonly VectorNode[], id: string): VectorMaskGroup | null {\n\tfor (const n of nodes) {\n\t\tif (n.type !== 'group') continue;\n\t\tif (isVectorMaskGroup(n) && n.children[0].id === id) return n;\n\t\tconst inner = vectorMaskOwner(n.children, id);\n\t\tif (inner) return inner;\n\t}\n\treturn null;\n}\n\n/** What agents and panels read about a mask group. */\nexport interface VectorMaskSummary {\n\ttype: VectorMaskType;\n\tinvert: boolean;\n\t/** Id of the mask (the group's first child). */\n\tmaskId: string;\n\t/** Ids of the masked contents, bottom to top. */\n\tcontents: string[];\n\t/** False while the mask child is hidden (the contents draw whole). */\n\tenabled: boolean;\n}\n\n/** A mask group's settings, mask and contents, or null for any other node. */\nexport function vectorMaskSummary(node: VectorNode | null | undefined): VectorMaskSummary | null {\n\tif (!isVectorMaskGroup(node)) return null;\n\treturn {\n\t\ttype: node.mask.type,\n\t\tinvert: !!node.mask.invert,\n\t\tmaskId: node.children[0].id,\n\t\tcontents: node.children.slice(1).map((c) => c.id),\n\t\tenabled: node.children[0].visible !== false\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Regions\n// ---------------------------------------------------------------------------\n\n/** One shape of a mask's region: a path (in the mask group's own space) and the rule that fills it. */\nexport interface VectorMaskRegionShape {\n\tpath: BezierPath;\n\tfillRule: VectorFillRule;\n}\n\nconst place = (path: BezierPath, m: AffineMatrix) => (isIdentityMatrix(m) ? path : transformBezierPath(path, m));\n\n/**\n * The area `node` covers as a mask of `type`, in the space `parent` maps it to: for a clip, the\n * outline of every visible path inside it (effects applied), whatever its paint; for an opacity\n * mask, where it paints (drawn fills, and drawn strokes as their outlines). A nested mask group\n * covers its masked result. `self` counts the node even when hidden (the mask itself).\n */\nexport function vectorMaskRegionShapes(node: VectorNode, type: VectorMaskType, parent: AffineMatrix = IDENTITY_MATRIX, self = true): VectorMaskRegionShape[] {\n\tif (!self && node.visible === false) return [];\n\tconst m = multiplyMatrices(nodeMatrix(node), parent);\n\tif (isVectorMaskActive(node) || (node.type === 'group' && node.effects?.length)) {\n\t\t// Drawn as shapes: a nested mask's masked result, or a group's effects applied.\n\t\treturn vectorAppearanceShapes(node).flatMap((s) => regionOfPath(s.path, s.source, type, m));\n\t}\n\tconst drawn = expandVectorAppearance(node);\n\tif (drawn.type === 'group') {\n\t\tconst inner = multiplyMatrices(nodeMatrix(drawn), parent);\n\t\treturn drawn.children.flatMap((c) => vectorMaskRegionShapes(c, type, inner, false));\n\t}\n\treturn regionOfPath(subpathsToBezier(drawn.subpaths), drawn, type, m);\n}\n\nfunction regionOfPath(path: BezierPath, style: VectorNode & { type: 'path' }, type: VectorMaskType, m: AffineMatrix): VectorMaskRegionShape[] {\n\tif (!path.length) return [];\n\tconst fillRule = style.fillRule ?? 'nonzero';\n\tif (type === 'clip') return [{ path: place(path, m), fillRule }];\n\tconst out: VectorMaskRegionShape[] = [];\n\tlet filled = false;\n\tfor (const l of vectorPaintLayerNodes(style)) {\n\t\tif (!l.drawn) continue;\n\t\tif (l.kind === 'fill') {\n\t\t\tif (!filled) out.push({ path: place(path, m), fillRule });\n\t\t\tfilled = true;\n\t\t} else if ((l.node.strokeWidth ?? 1) > 0) {\n\t\t\tconst area = vectorStrokeOutlinePath(path, l.node);\n\t\t\tif (area.length) out.push({ path: place(area, m), fillRule: 'nonzero' });\n\t\t}\n\t}\n\treturn out;\n}\n\nconst regionCache = new WeakMap<VectorMaskGroup, VectorMaskRegionShape[]>();\n\n/** The region of a mask group's mask in the group's own space (not inverted). Cached per group object. */\nexport function vectorMaskRegion(group: VectorMaskGroup): VectorMaskRegionShape[] {\n\tlet hit = regionCache.get(group);\n\tif (!hit) {\n\t\thit = vectorMaskRegionShapes(group.children[0], group.mask.type);\n\t\tregionCache.set(group, hit);\n\t}\n\treturn hit;\n}\n\nconst unionCache = new WeakMap<VectorMaskGroup, BezierPath>();\n\n/** The mask's region as one clean path (a boolean union, read nonzero or even-odd alike). Cached per group object. */\nexport function vectorMaskRegionPath(group: VectorMaskGroup): BezierPath {\n\tlet hit = unionCache.get(group);\n\tif (!hit) {\n\t\tconst shapes = vectorMaskRegion(group);\n\t\thit = !shapes.length ? [] : shapes.length === 1 && shapes[0].fillRule === 'nonzero' ? shapes[0].path : pathBoolean('union', shapes.map((s) => ({ path: s.path, fillRule: s.fillRule }))).path;\n\t\tunionCache.set(group, hit);\n\t}\n\treturn hit;\n}\n\n/** Whether a point (in the group's own space) is inside the mask's region, `tolerance` past its edge counting. */\nfunction inRegion(group: VectorMaskGroup, p: Vec2, tolerance: number): boolean {\n\tconst pt: [number, number] = [p.x, p.y];\n\tfor (const s of vectorMaskRegion(group)) {\n\t\tif (pointInBezierPath(s.path, pt, s.fillRule)) return true;\n\t\tif (tolerance > 0 && distanceToBezierPath(s.path, pt) <= tolerance) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Whether a mask group's mask lets point `p` (the group's own space) through: inside its region,\n * or outside it when inverted. `tolerance` widens what counts as through (its edge, either way).\n * Always true while the mask is off.\n */\nexport function vectorMaskContains(group: VectorMaskGroup, p: Vec2, tolerance = 0): boolean {\n\tif (!isVectorMaskActive(group)) return true;\n\tif (group.mask.type !== 'clip') {\n\t\t// Opacity masks: where the mask paints, by its own hit-test (strokes, alignment, arrowheads).\n\t\tconst source = group.children[0];\n\t\tconst hit = hitTestVectorNode(source.visible === false ? { ...source, visible: true } : source, p, group.mask.invert ? 0 : tolerance);\n\t\treturn group.mask.invert ? !hit || (tolerance > 0 && hitNearEdge(group, p, tolerance)) : hit;\n\t}\n\tif (!group.mask.invert) return inRegion(group, p, tolerance);\n\treturn !inRegion(group, p, 0) || hitNearEdge(group, p, tolerance);\n}\n\nfunction hitNearEdge(group: VectorMaskGroup, p: Vec2, tolerance: number): boolean {\n\tif (tolerance <= 0) return false;\n\treturn vectorMaskRegion(group).some((s) => distanceToBezierPath(s.path, [p.x, p.y]) <= tolerance);\n}\n\n// ---------------------------------------------------------------------------\n// Masked shapes\n// ---------------------------------------------------------------------------\n\n/** A path's open subpaths cut by the region (kept inside it, or outside when inverted). */\nfunction clipOpen(path: BezierPath, region: BezierPath, invert: boolean): BezierPath {\n\tconst out: BezierPath = [];\n\tfor (const sp of bezierToSubpaths(path)) {\n\t\tconst pieces = eraseFromOutline(sp, region, { keep: invert ? 'outside' : 'inside' });\n\t\t// Null: the outline never crosses the region's edge, so it is all in or all out.\n\t\tif (pieces === null) {\n\t\t\tconst a = sp.anchors[0];\n\t\t\tconst inside = !!a && pointInBezierPath(region, [a.x, a.y], 'nonzero');\n\t\t\tif (inside !== invert) out.push(...subpathsToBezier([sp]));\n\t\t} else out.push(...subpathsToBezier(pieces));\n\t}\n\treturn out;\n}\n\n/** A shape's path clipped by the mask's region: booleans for its closed contours, cuts for its open ones. */\nfunction clipShapePath(path: BezierPath, fillRule: VectorFillRule, region: BezierPath, invert: boolean): BezierPath {\n\tconst subpaths = bezierToSubpaths(path);\n\tconst closed = subpaths.filter((sp) => sp.closed);\n\tconst open = subpaths.filter((sp) => !sp.closed);\n\tconst out: BezierPath = [];\n\tif (closed.length) {\n\t\tconst operands = [{ path: subpathsToBezier(closed), fillRule }, { path: region, fillRule: 'nonzero' as const }];\n\t\tout.push(...(region.length ? pathBoolean(invert ? 'difference' : 'intersection', operands).path : invert ? operands[0].path : []));\n\t}\n\tif (open.length) out.push(...(region.length ? clipOpen(subpathsToBezier(open), region, invert) : invert ? subpathsToBezier(open) : []));\n\treturn out;\n}\n\n/**\n * What a mask group draws as shapes in its own space: its contents' shapes (their own effects\n * applied, then the group's), each clipped by the mask's region. The appearance code calls this\n * for mask groups (`vectorAppearanceShapes`), so use that instead (it caches).\n */\nexport function vectorMaskedShapes(group: VectorMaskGroup): VectorAppearanceShape[] {\n\tconst contents = group.children.flatMap((c, i) => (i === 0 ? [] : vectorChildAppearanceShapes(c, i)));\n\tconst shapes = applyVectorEffects(contents, group.effects);\n\tif (!isVectorMaskActive(group)) return shapes;\n\tconst region = vectorMaskRegionPath(group);\n\tconst invert = !!group.mask.invert;\n\tconst out: VectorAppearanceShape[] = [];\n\tfor (const s of shapes) {\n\t\tconst path = clipShapePath(s.path, s.source.fillRule ?? 'nonzero', region, invert);\n\t\t// Keep the shape's evaluated paths in the clip's clean nonzero form.\n\t\tif (path.length) out.push({ ...s, path, source: s.source.fillRule === 'evenodd' ? { ...s.source, fillRule: 'nonzero' } : s.source });\n\t}\n\treturn out;\n}\n\n/**\n * The node with every working mask group inside it replaced by a plain group of its masked shapes\n * (ids `<group id>~fx<n>`, placement kept): the geometry booleans, the Pathfinder, the Shape\n * Builder and the paint bucket work with. The node itself when it holds no mask.\n */\nexport function vectorMaskedGeometry(node: VectorNode): VectorNode {\n\tif (node.type !== 'group') return node;\n\tif (isVectorMaskActive(node)) {\n\t\treturn {\n\t\t\tid: node.id,\n\t\t\ttype: 'group',\n\t\t\t...(node.transform ? { transform: node.transform } : {}),\n\t\t\t...(node.opacity !== undefined ? { opacity: node.opacity } : {}),\n\t\t\t...(node.visible === false ? { visible: false } : {}),\n\t\t\t...(node.locked ? { locked: true } : {}),\n\t\t\tchildren: vectorAppearanceNodes(node)\n\t\t};\n\t}\n\tlet changed = false;\n\tconst children = node.children.map((c) => {\n\t\tconst next = vectorMaskedGeometry(c);\n\t\tif (next !== c) changed = true;\n\t\treturn next;\n\t});\n\treturn changed ? { ...node, children } : node;\n}\n\n/** Bounds of a mask group's masked result in its own space (stroke widths not included), or null. */\nexport function vectorMaskedLocalBounds(group: VectorMaskGroup): VectorBounds | null {\n\tconst b = bezierPathBounds(vectorAppearanceShapes(group).flatMap((s) => s.path));\n\treturn b ? { x: b.minX, y: b.minY, width: b.maxX - b.minX, height: b.maxY - b.minY } : null;\n}\n\n// ---------------------------------------------------------------------------\n// Render data\n// ---------------------------------------------------------------------------\n\n/** How far anything inside a node draws past its geometry: strokes, and filters on the way down. */\nfunction paintReach(node: VectorNode): number {\n\tconst own = vectorFilterReach(activeVectorFilters(node));\n\tif (node.type === 'path') return own + vectorStrokeStackReach(node);\n\treturn own + Math.max(0, ...node.children.map(paintReach));\n}\n\n/**\n * The box a mask group's contents can draw over, in its own space (strokes and filters included,\n * with a margin): the `<mask>` region, the filter region of an inverted opacity mask and the\n * rectangle an inverted clip cuts the mask out of. Null without contents.\n */\nexport function vectorMaskContentBounds(group: VectorMaskGroup): VectorBounds | null {\n\tconst contents = group.children.slice(1);\n\tconst b = unionBounds(contents.map((c) => vectorNodeBounds(c, IDENTITY_MATRIX)));\n\tif (!b) return null;\n\tconst pad = Math.max(0, ...contents.map(paintReach)) + Math.max(b.width, b.height) * 0.05 + 4;\n\treturn { x: b.x - pad, y: b.y - pad, width: b.width + 2 * pad, height: b.height + 2 * pad };\n}\n\n/** One path of a clipping mask's `<clipPath>` (group space). */\nexport interface VectorMaskClipShape {\n\td: string;\n\tclipRule: VectorFillRule;\n}\n\n/**\n * How to draw a mask group's mask, in the group's own space. Draw the group's own `<g>` (transform,\n * opacity, blend mode, filter) holding an inner `<g>` with the contents, referencing:\n * - `clip`: a `<clipPath id>` holding each of `clip` as `<path d clip-rule>`;\n * - `alpha` / `luminance`: a `<mask id maskUnits=\"userSpaceOnUse\" x y width height>` over\n * `bounds` (with `mask-type=\"alpha\"` for alpha) holding the mask node as drawn, wrapped in\n * `<g filter={filter}>` when `filter` is set (an inverted mask).\n */\nexport interface VectorMaskRenderData {\n\ttype: VectorMaskType;\n\tinvert: boolean;\n\t/** Id of the `<clipPath>` or `<mask>` element. */\n\tid: string;\n\t/** Clip shapes (clipping masks). An inverted clip is one even-odd shape: a rectangle with the mask's outline cut out. */\n\tclip?: VectorMaskClipShape[];\n\t/** Region of the `<mask>` (opacity masks). */\n\tbounds?: VectorBounds;\n\t/** `url(#id)` of the inverting filter (inverted opacity masks; in `filters`). */\n\tfilter?: string;\n\t/** The inverting filter itself (inverted opacity masks). */\n\tinvertFilter?: VectorRenderFilter;\n}\n\nconst fe = (tag: string, attrs: Record<string, string | number>): VectorFilterPrimitive => ({ tag, attrs });\nconst LUMA = '-0.2125 -0.7154 -0.0721 0 1';\n\n/** The filter that inverts an opacity mask over `bounds`: alpha → 1 − alpha, or brightness → 1 − brightness over black. */\nfunction invertFilter(type: 'alpha' | 'luminance', id: string, bounds: VectorBounds): VectorRenderFilter {\n\tconst primitives =\n\t\ttype === 'alpha'\n\t\t\t? [fe('feColorMatrix', { in: 'SourceGraphic', type: 'matrix', values: '0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 1' })]\n\t\t\t: [\n\t\t\t\t\t// Over black first, so a transparent area reads as dark (hidden) before inverting.\n\t\t\t\t\tfe('feFlood', { 'flood-color': '#000000', result: 'black' }),\n\t\t\t\t\tfe('feComposite', { in: 'SourceGraphic', in2: 'black', operator: 'over', result: 'flat' }),\n\t\t\t\t\tfe('feColorMatrix', { in: 'flat', type: 'matrix', values: `${LUMA} ${LUMA} ${LUMA} 0 0 0 0 1` })\n\t\t\t\t];\n\tconst r = (n: number) => Math.round(n * 1000) / 1000;\n\treturn { id, x: r(bounds.x), y: r(bounds.y), width: r(bounds.width), height: r(bounds.height), primitives };\n}\n\nconst rectPath = (b: VectorBounds): BezierPath => [\n\t{ type: 'M', x: b.x, y: b.y },\n\t{ type: 'L', x: b.x + b.width, y: b.y },\n\t{ type: 'L', x: b.x + b.width, y: b.y + b.height },\n\t{ type: 'L', x: b.x, y: b.y + b.height },\n\t{ type: 'Z' }\n];\n\n/**\n * A mask group's mask as render data with element id `id` (and `filterId` for an inverting\n * filter), or null when the mask is off or the group has no contents. Shared by the canvases and\n * SVG export.\n */\nexport function vectorMaskRenderData(group: VectorMaskGroup, id: string, filterId: string, precision = 3): VectorMaskRenderData | null {\n\tif (!isVectorMaskActive(group) || group.children.length < 2) return null;\n\tconst invert = !!group.mask.invert;\n\tconst bounds = vectorMaskContentBounds(group);\n\tif (group.mask.type === 'clip') {\n\t\tconst shapes = vectorMaskRegion(group);\n\t\tif (!invert) return { type: 'clip', invert, id, clip: shapes.map((s) => ({ d: bezierPathToSvg(s.path, { precision }), clipRule: s.fillRule })) };\n\t\t// Inverted (pdfcraft `clip_out`): a rectangle round the contents with the mask's union cut out, even-odd.\n\t\tconst box = bounds ?? { x: 0, y: 0, width: 0, height: 0 };\n\t\tconst region = vectorMaskRegionPath(group);\n\t\treturn { type: 'clip', invert, id, clip: [{ d: bezierPathToSvg([...rectPath(box), ...region], { precision }), clipRule: 'evenodd' }] };\n\t}\n\tif (!bounds) return null;\n\tif (!invert) return { type: group.mask.type, invert, id, bounds };\n\tconst filter = invertFilter(group.mask.type, filterId, bounds);\n\treturn { type: group.mask.type, invert, id, bounds, filter: `url(#${filterId})`, invertFilter: filter };\n}\n","/**\n * Warp geometry for envelope distortion: preset warp styles and editable Bézier meshes, both\n * forward maps from a source box (the bounds of the paths an envelope bends) to the node's space.\n * The maths is shared with the image editor's Free Transform and lives in `src/geom/warp.ts`;\n * these are its vector-named exports, plus the envelope-from-outline helpers.\n *\n * The Coons patch of `vectorMeshFromOutline` is our own.\n */\nimport {\n\tclampMeshCount,\n\tevenKnots,\n\tfitWarpMesh,\n\tlerpVec2,\n\twarpMeshColumns,\n\twarpMeshRows\n} from '../geom/warp.js';\nimport type { Vec2, VectorSubpath, VectorWarpMesh } from './types.js';\n\nexport {\n\tWARP_STYLES as VECTOR_WARP_STYLES,\n\tWARP_STYLE_LABELS as VECTOR_WARP_STYLE_LABELS,\n\twarpPresetGrid as vectorWarpPresetGrid,\n\twarpPresetMesh as vectorWarpPresetMesh,\n\twarpMeshColumns as vectorMeshColumns,\n\twarpMeshRows as vectorMeshRows,\n\tisValidWarpMesh as isValidVectorMesh,\n\tfitWarpMesh as fitVectorMesh,\n\tidentityWarpMesh as vectorIdentityMesh,\n\tevalWarpMesh as evalVectorMesh,\n\tsplitWarpMeshU as splitVectorMeshU,\n\tsplitWarpMeshV as splitVectorMeshV,\n\tremoveWarpMeshSplitU as removeVectorMeshSplitU,\n\tremoveWarpMeshSplitV as removeVectorMeshSplitV,\n\tmapWarpMeshPoints as mapVectorMeshPoints,\n\tresizeWarpMesh as resizeVectorMesh,\n\twarpMeshControlBounds as vectorMeshControlBounds,\n\twarpMeshParamAt as vectorMeshParamAt,\n\tdragWarpMeshPoint as dragVectorMeshPoint,\n\tpullWarpMesh as pullVectorMesh,\n\twarpMeshEditPoints as vectorMeshEditPoints,\n\twarpMeshGridCurves as vectorMeshGridCurves\n} from '../geom/warp.js';\n\n/** A preset warp's settings (bend and distortions in percent, −100…100). */\nexport type { WarpPreset as VectorWarpPreset } from '../geom/warp.js';\n\n// ---------------------------------------------------------------------------\n// Envelope from an outline (Make with Top Object)\n// ---------------------------------------------------------------------------\n\n/** Points along a polyline by fraction of its length. */\nfunction polylineAt(pts: readonly Vec2[]): (u: number) => Vec2 {\n\tconst acc = [0];\n\tfor (let i = 1; i < pts.length; i++) acc.push(acc[i - 1] + Math.hypot(pts[i].x - pts[i - 1].x, pts[i].y - pts[i - 1].y));\n\tconst total = acc[acc.length - 1];\n\treturn (u) => {\n\t\tif (!(total > 0)) return pts[0];\n\t\tconst L = Math.min(1, Math.max(0, u)) * total;\n\t\tlet i = 1;\n\t\twhile (i < acc.length - 1 && acc[i] < L) i++;\n\t\tconst seg = acc[i] - acc[i - 1];\n\t\treturn lerpVec2(pts[i - 1], pts[i], seg > 0 ? (L - acc[i - 1]) / seg : 0);\n\t};\n}\n\nfunction sampleSubpath(sp: VectorSubpath, per = 24): Vec2[] {\n\tconst n = sp.anchors.length;\n\tconst out: Vec2[] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tconst a = sp.anchors[i];\n\t\tconst b = sp.anchors[(i + 1) % n];\n\t\tconst c = [{ x: a.x, y: a.y }, a.out ?? a, b.in ?? b, { x: b.x, y: b.y }];\n\t\tfor (let k = 0; k < per; k++) {\n\t\t\tconst t = k / per;\n\t\t\tconst ab = lerpVec2(c[0], c[1], t);\n\t\t\tconst bc = lerpVec2(c[1], c[2], t);\n\t\t\tconst cd = lerpVec2(c[2], c[3], t);\n\t\t\tout.push(lerpVec2(lerpVec2(ab, bc, t), lerpVec2(bc, cd, t), t));\n\t\t}\n\t}\n\treturn out;\n}\n\n/**\n * A mesh whose border follows a closed outline (Illustrator's Make with Top Object): the outline is\n * cut into four sides at the points nearest its bounding box's corners, and the interior is the\n * Coons patch of those sides, fitted with `columns × rows` patches. Null for an open or degenerate\n * outline.\n */\nexport function vectorMeshFromOutline(sp: VectorSubpath, columns = 4, rows = 4): VectorWarpMesh | null {\n\tif (!sp.closed || sp.anchors.length < 2) return null;\n\tlet ring = sampleSubpath(sp);\n\tconst xs = ring.map((p) => p.x);\n\tconst ys = ring.map((p) => p.y);\n\tconst box = { x0: Math.min(...xs), y0: Math.min(...ys), x1: Math.max(...xs), y1: Math.max(...ys) };\n\tif (!(box.x1 - box.x0 > 1e-9 && box.y1 - box.y0 > 1e-9)) return null;\n\tconst nearest = (c: Vec2) => {\n\t\tlet best = 0;\n\t\tfor (let i = 1; i < ring.length; i++) if (Math.hypot(ring[i].x - c.x, ring[i].y - c.y) < Math.hypot(ring[best].x - c.x, ring[best].y - c.y)) best = i;\n\t\treturn best;\n\t};\n\tconst cornersOf = () =>\n\t\t[\n\t\t\t{ x: box.x0, y: box.y0 },\n\t\t\t{ x: box.x1, y: box.y0 },\n\t\t\t{ x: box.x1, y: box.y1 },\n\t\t\t{ x: box.x0, y: box.y1 }\n\t\t].map(nearest);\n\tlet c = cornersOf();\n\tconst n = ring.length;\n\tconst fwd = (from: number, to: number) => (to - from + n) % n;\n\t// Clockwise on screen: top-left, top-right, bottom-right, bottom-left in walking order.\n\tif (!(fwd(c[0], c[1]) < fwd(c[0], c[2]) && fwd(c[0], c[2]) < fwd(c[0], c[3]))) {\n\t\tring = ring.slice().reverse();\n\t\tc = cornersOf();\n\t\tif (!(fwd(c[0], c[1]) < fwd(c[0], c[2]) && fwd(c[0], c[2]) < fwd(c[0], c[3]))) return null;\n\t}\n\tif (new Set(c).size < 4) return null;\n\tconst walk = (from: number, to: number) => {\n\t\tconst out: Vec2[] = [];\n\t\tfor (let i = from; ; i = (i + 1) % n) {\n\t\t\tout.push(ring[i]);\n\t\t\tif (i === to) break;\n\t\t}\n\t\treturn out;\n\t};\n\t// The ring runs clockwise, so the bottom (bottom-right → bottom-left) and left (bottom-left →\n\t// top-left) walks are reversed to run left to right and top to bottom.\n\tconst top = polylineAt(walk(c[0], c[1]));\n\tconst right = polylineAt(walk(c[1], c[2]));\n\tconst bottomLR = polylineAt(walk(c[2], c[3]).reverse());\n\tconst leftTB = polylineAt(walk(c[3], c[0]).reverse());\n\tconst p00 = top(0);\n\tconst p10 = top(1);\n\tconst p01 = bottomLR(0);\n\tconst p11 = bottomLR(1);\n\tconst coons = (s: number, t: number): Vec2 => {\n\t\tconst T = top(s);\n\t\tconst B = bottomLR(s);\n\t\tconst L = leftTB(t);\n\t\tconst R = right(t);\n\t\treturn {\n\t\t\tx: (1 - t) * T.x + t * B.x + (1 - s) * L.x + s * R.x - ((1 - s) * (1 - t) * p00.x + s * (1 - t) * p10.x + (1 - s) * t * p01.x + s * t * p11.x),\n\t\t\ty: (1 - t) * T.y + t * B.y + (1 - s) * L.y + s * R.y - ((1 - s) * (1 - t) * p00.y + s * (1 - t) * p10.y + (1 - s) * t * p01.y + s * t * p11.y)\n\t\t};\n\t};\n\treturn fitWarpMesh(coons, evenKnots(clampMeshCount(columns)), evenKnots(clampMeshCount(rows)));\n}\n\n/** The mesh's border as one closed subpath (clockwise from the top-left corner): what Release gives back as the envelope's shape. */\nexport function vectorMeshOutline(m: VectorWarpMesh): VectorSubpath {\n\tconst nx = warpMeshColumns(m);\n\tconst ny = warpMeshRows(m);\n\tconst pt = (i: number, j: number) => m.points[j * nx + i];\n\t// The border as a chain of cubics, each [p0, p1, p2, p3].\n\tconst runs: Vec2[][] = [];\n\tfor (let i = 0; i + 3 < nx; i += 3) runs.push([pt(i, 0), pt(i + 1, 0), pt(i + 2, 0), pt(i + 3, 0)]);\n\tfor (let j = 0; j + 3 < ny; j += 3) runs.push([pt(nx - 1, j), pt(nx - 1, j + 1), pt(nx - 1, j + 2), pt(nx - 1, j + 3)]);\n\tfor (let i = nx - 1; i - 3 >= 0; i -= 3) runs.push([pt(i, ny - 1), pt(i - 1, ny - 1), pt(i - 2, ny - 1), pt(i - 3, ny - 1)]);\n\tfor (let j = ny - 1; j - 3 >= 0; j -= 3) runs.push([pt(0, j), pt(0, j - 1), pt(0, j - 2), pt(0, j - 3)]);\n\tconst anchors = runs.map((r, k) => {\n\t\tconst prev = runs[(k + runs.length - 1) % runs.length];\n\t\treturn { x: r[0].x, y: r[0].y, in: { ...prev[2] }, out: { ...r[1] } };\n\t});\n\treturn { closed: true, anchors };\n}\n","/**\n * Live appearance: path effect stacks and live boolean groups, evaluated on demand while the\n * stored anchors and children stay editable.\n *\n * `expandVectorAppearance` is the one place the effected geometry is made (cached per node\n * object, so an edit recomputes only the nodes it replaced). Rendering, hit-testing, bounds,\n * snapping, booleans, the Pathfinder, the Shape Builder and SVG export all read it, the way they\n * read live corners through `subpathsToBezier`; Expand Appearance stores it.\n *\n * Stacking follows After Effects' shape layers (effectcraft `crates/render/src/shapes.rs`): an\n * effect acts on every path above it — the node's own outline, or for a group every path of its\n * children (nested groups included, as drawn by their own effects) — in the order of the stack,\n * and effects placed after a repeater act on its copies. The result paints with the style of the\n * path each shape came from; Merge Paths and live booleans paint with the bottom one's.\n *\n * Live booleans fold their operands bottom to top, each combined with the result below it by its\n * own operation or the group's, and every prefix of the fold is cached under a rolling hash of the\n * operands it covers, so editing one operand recombines only the operands from it up; the cache\n * drops its least recently used half when full. Live groups are hit by their result except while\n * they are being edited (`vectorEnteredGroups`).\n *\n * Ported from ArtCraft (MIT OR Apache-2.0, © ArtCraft Team and contributors; see\n * THIRD_PARTY_NOTICES.md): the stacking rule of effectcraft `crates/render/src/shapes.rs`\n * (`collect()`); figcraft `crates/render/src/boolean.rs` (`fold_cached`, `PathCache`) and\n * `crates/engine/src/booleans.rs` (`operand_op`, `result_outline`); and the live-object model of\n * vectorcraft `crates/doc/src/live.rs` (`expand_live`, `expanded_group`, `subtree_has_live`).\n */\nimport { bezierPathBounds, transformBezierPath, type AffineMatrix, type BezierPath } from '../canvas/pathops/bezier.js';\nimport { pathBoolean } from '../canvas/pathops/boolean.js';\nimport { bezierToSubpaths, isIdentityMatrix, subpathsToBezier } from './geometry.js';\nimport {\n\tmergePaths,\n\toffsetPaths,\n\tpathsCenter,\n\tpuckerBloatPaths,\n\trepeatPaths,\n\troundCornerPaths,\n\ttrimPaths,\n\ttrimPathsIndividually,\n\ttwistPaths,\n\twigglePaths,\n\tzigzagPaths\n} from './pathEffects.js';\nimport { mapBezierPath } from './pathMap.js';\nimport { isVectorMaskGroup, vectorMaskedShapes } from './mask.js';\nimport {\n\tVECTOR_WARP_STYLES,\n\tVECTOR_WARP_STYLE_LABELS,\n\tevalVectorMesh,\n\tisValidVectorMesh,\n\tmapVectorMeshPoints,\n\tresizeVectorMesh,\n\tvectorIdentityMesh,\n\tvectorWarpPresetMesh\n} from './warp.js';\nimport type {\n\tVectorBounds,\n\tVec2,\n\tVectorBooleanOperation,\n\tVectorEffect,\n\tVectorEffectParamsMap,\n\tVectorEffectType,\n\tVectorFillRule,\n\tVectorGroupNode,\n\tVectorNode,\n\tVectorPathNode,\n\tVectorWarpMesh\n} from './types.js';\n\n/** One shape of a node's appearance: a path in the node's own space, painted in `source`'s style. */\nexport interface VectorAppearanceShape {\n\t/** The path whose style paints it. */\n\tsource: VectorPathNode;\n\tpath: BezierPath;\n\t/** Opacity factor relative to the node (its own opacity not included). */\n\topacity: number;\n\t/** Draw-order key (compared element by element). */\n\torder: number[];\n}\n\n// ---------------------------------------------------------------------------\n// Effect definitions\n// ---------------------------------------------------------------------------\n\n/** Default parameters of each effect type. */\nexport const VECTOR_EFFECT_DEFAULTS: { readonly [K in VectorEffectType]: VectorEffectParamsMap[K] } = {\n\ttrim: { start: 0, end: 50, offset: 0, mode: 'simultaneous' },\n\troundCorners: { radius: 10 },\n\tpuckerBloat: { amount: 30 },\n\ttwist: { angle: 45, center: { x: 0, y: 0 } },\n\tzigzag: { size: 5, ridges: 4, smooth: false },\n\twiggle: { size: 5, detail: 6, smooth: true, speed: 2, correlation: 50, phase: 0, seed: 1, time: 0 },\n\toffset: { amount: 5, join: 'miter', miterLimit: 4, copies: 1, copyOffset: 0 },\n\tmerge: { mode: 'add' },\n\trepeater: {\n\t\tcopies: 3,\n\t\toffset: 0,\n\t\tposition: { x: 100, y: 0 },\n\t\tanchor: { x: 0, y: 0 },\n\t\trotation: 0,\n\t\tscale: { x: 100, y: 100 },\n\t\tstartOpacity: 100,\n\t\tendOpacity: 100,\n\t\tcomposite: 'below'\n\t},\n\tenvelope: { style: 'arc', bend: 50, horizontal: 0, vertical: 0, orientation: 'horizontal', rows: 2, columns: 2, mesh: null }\n};\n\nexport const VECTOR_EFFECT_LABELS: Record<VectorEffectType, string> = {\n\ttrim: 'Trim Paths',\n\troundCorners: 'Round Corners',\n\tpuckerBloat: 'Pucker & Bloat',\n\ttwist: 'Twist',\n\tzigzag: 'Zig Zag',\n\twiggle: 'Wiggle Paths',\n\toffset: 'Offset Paths',\n\tmerge: 'Merge Paths',\n\trepeater: 'Repeater',\n\tenvelope: 'Envelope'\n};\n\n/** How a panel edits one effect parameter. */\nexport type VectorEffectParamSpec =\n\t| { key: string; label: string; kind: 'number'; min?: number; max?: number; step: number; slider?: boolean }\n\t| { key: string; label: string; kind: 'vec2'; step: number }\n\t| { key: string; label: string; kind: 'boolean' }\n\t| { key: string; label: string; kind: 'select'; options: { value: string; label: string }[] };\n\n/** The editable parameters of each effect type, in panel order. */\nexport const VECTOR_EFFECT_PARAM_SPECS: Record<VectorEffectType, VectorEffectParamSpec[]> = {\n\ttrim: [\n\t\t{ key: 'start', label: 'Start %', kind: 'number', min: 0, max: 100, step: 1, slider: true },\n\t\t{ key: 'end', label: 'End %', kind: 'number', min: 0, max: 100, step: 1, slider: true },\n\t\t{ key: 'offset', label: 'Offset °', kind: 'number', min: -360, max: 360, step: 1, slider: true },\n\t\t{\n\t\t\tkey: 'mode',\n\t\t\tlabel: 'Paths',\n\t\t\tkind: 'select',\n\t\t\toptions: [\n\t\t\t\t{ value: 'simultaneous', label: 'Simultaneously' },\n\t\t\t\t{ value: 'individual', label: 'Individually' }\n\t\t\t]\n\t\t}\n\t],\n\troundCorners: [{ key: 'radius', label: 'Radius', kind: 'number', min: 0, max: 200, step: 1, slider: true }],\n\tpuckerBloat: [{ key: 'amount', label: 'Amount %', kind: 'number', min: -100, max: 100, step: 1, slider: true }],\n\ttwist: [\n\t\t{ key: 'angle', label: 'Angle °', kind: 'number', min: -720, max: 720, step: 1, slider: true },\n\t\t{ key: 'center', label: 'Centre', kind: 'vec2', step: 1 }\n\t],\n\tzigzag: [\n\t\t{ key: 'size', label: 'Size', kind: 'number', min: 0, max: 100, step: 0.5, slider: true },\n\t\t{ key: 'ridges', label: 'Ridges', kind: 'number', min: 0, max: 50, step: 1, slider: true },\n\t\t{ key: 'smooth', label: 'Smooth', kind: 'boolean' }\n\t],\n\twiggle: [\n\t\t{ key: 'size', label: 'Size', kind: 'number', min: 0, max: 100, step: 0.5, slider: true },\n\t\t{ key: 'detail', label: 'Detail', kind: 'number', min: 0, max: 50, step: 1, slider: true },\n\t\t{ key: 'smooth', label: 'Smooth', kind: 'boolean' },\n\t\t{ key: 'correlation', label: 'Correlation %', kind: 'number', min: 0, max: 100, step: 1, slider: true },\n\t\t{ key: 'speed', label: 'Speed', kind: 'number', min: 0, step: 0.1 },\n\t\t{ key: 'phase', label: 'Phase °', kind: 'number', step: 1 },\n\t\t{ key: 'time', label: 'Time (s)', kind: 'number', min: 0, max: 10, step: 0.05, slider: true },\n\t\t{ key: 'seed', label: 'Seed', kind: 'number', step: 1 }\n\t],\n\toffset: [\n\t\t{ key: 'amount', label: 'Amount', kind: 'number', min: -100, max: 100, step: 0.5, slider: true },\n\t\t{\n\t\t\tkey: 'join',\n\t\t\tlabel: 'Join',\n\t\t\tkind: 'select',\n\t\t\toptions: [\n\t\t\t\t{ value: 'miter', label: 'Miter' },\n\t\t\t\t{ value: 'round', label: 'Round' },\n\t\t\t\t{ value: 'bevel', label: 'Bevel' }\n\t\t\t]\n\t\t},\n\t\t{ key: 'miterLimit', label: 'Miter limit', kind: 'number', min: 1, step: 0.5 },\n\t\t{ key: 'copies', label: 'Copies', kind: 'number', min: 1, max: 20, step: 1 },\n\t\t{ key: 'copyOffset', label: 'Copy offset', kind: 'number', step: 0.5 }\n\t],\n\tmerge: [\n\t\t{\n\t\t\tkey: 'mode',\n\t\t\tlabel: 'Mode',\n\t\t\tkind: 'select',\n\t\t\toptions: [\n\t\t\t\t{ value: 'merge', label: 'Merge' },\n\t\t\t\t{ value: 'add', label: 'Add' },\n\t\t\t\t{ value: 'subtract', label: 'Subtract' },\n\t\t\t\t{ value: 'intersect', label: 'Intersect' },\n\t\t\t\t{ value: 'exclude', label: 'Exclude' }\n\t\t\t]\n\t\t}\n\t],\n\trepeater: [\n\t\t{ key: 'copies', label: 'Copies', kind: 'number', min: 0, max: 50, step: 0.1, slider: true },\n\t\t{ key: 'offset', label: 'Offset', kind: 'number', min: -10, max: 10, step: 0.1, slider: true },\n\t\t{ key: 'position', label: 'Position', kind: 'vec2', step: 1 },\n\t\t{ key: 'anchor', label: 'Anchor', kind: 'vec2', step: 1 },\n\t\t{ key: 'rotation', label: 'Rotation °', kind: 'number', min: -360, max: 360, step: 1, slider: true },\n\t\t{ key: 'scale', label: 'Scale %', kind: 'vec2', step: 1 },\n\t\t{ key: 'startOpacity', label: 'Start opacity %', kind: 'number', min: 0, max: 100, step: 1, slider: true },\n\t\t{ key: 'endOpacity', label: 'End opacity %', kind: 'number', min: 0, max: 100, step: 1, slider: true },\n\t\t{\n\t\t\tkey: 'composite',\n\t\t\tlabel: 'Copies',\n\t\t\tkind: 'select',\n\t\t\toptions: [\n\t\t\t\t{ value: 'below', label: 'Below' },\n\t\t\t\t{ value: 'above', label: 'Above' }\n\t\t\t]\n\t\t}\n\t],\n\tenvelope: [\n\t\t{\n\t\t\tkey: 'style',\n\t\t\tlabel: 'Warp',\n\t\t\tkind: 'select',\n\t\t\toptions: [...VECTOR_WARP_STYLES.map((value) => ({ value, label: VECTOR_WARP_STYLE_LABELS[value] })), { value: 'mesh', label: 'Mesh' }]\n\t\t},\n\t\t{ key: 'bend', label: 'Bend %', kind: 'number', min: -100, max: 100, step: 1, slider: true },\n\t\t{ key: 'horizontal', label: 'Horizontal distortion %', kind: 'number', min: -100, max: 100, step: 1, slider: true },\n\t\t{ key: 'vertical', label: 'Vertical distortion %', kind: 'number', min: -100, max: 100, step: 1, slider: true },\n\t\t{\n\t\t\tkey: 'orientation',\n\t\t\tlabel: 'Orientation',\n\t\t\tkind: 'select',\n\t\t\toptions: [\n\t\t\t\t{ value: 'horizontal', label: 'Horizontal' },\n\t\t\t\t{ value: 'vertical', label: 'Vertical' }\n\t\t\t]\n\t\t},\n\t\t{ key: 'rows', label: 'Mesh rows', kind: 'number', min: 1, max: 16, step: 1 },\n\t\t{ key: 'columns', label: 'Mesh columns', kind: 'number', min: 1, max: 16, step: 1 }\n\t]\n};\n\n/** Envelope parameters. */\nexport type VectorEnvelopeParams = VectorEffectParamsMap['envelope'];\n\nconst cloneMesh = (m: VectorWarpMesh): VectorWarpMesh => ({ us: m.us.slice(), vs: m.vs.slice(), points: m.points.map((p) => ({ x: p.x, y: p.y })) });\n\n/**\n * The envelope's mesh over `bounds` (the paths' combined bounds), in the node's space: its own\n * mesh, the flat mesh of `columns × rows` patches, or a preset split exactly into that many patches.\n */\nexport function vectorEnvelopeMesh(params: VectorEnvelopeParams, bounds: VectorBounds): VectorWarpMesh {\n\tif (params.style === 'mesh') return params.mesh ?? vectorIdentityMesh(bounds, params.columns, params.rows);\n\tconst preset = vectorWarpPresetMesh({ style: params.style, bend: params.bend, horizontal: params.horizontal, vertical: params.vertical, orientation: params.orientation }, bounds);\n\treturn preset ? resizeVectorMesh(preset, params.columns, params.rows) : vectorIdentityMesh(bounds, params.columns, params.rows);\n}\n\n/** The envelope as a map of the node's space over `bounds`, or null when it changes nothing (or the box is empty). */\nexport function vectorEnvelopeMap(params: VectorEnvelopeParams, bounds: VectorBounds | null): ((p: { x: number; y: number }) => { x: number; y: number }) | null {\n\tif (!bounds || !(bounds.width > 0 && bounds.height > 0)) return null;\n\tconst mesh =\n\t\tparams.style === 'mesh'\n\t\t\t? params.mesh\n\t\t\t: vectorWarpPresetMesh({ style: params.style, bend: params.bend, horizontal: params.horizontal, vertical: params.vertical, orientation: params.orientation }, bounds);\n\tif (!mesh) return null;\n\tconst { x, y, width, height } = bounds;\n\treturn (p) => evalVectorMesh(mesh, (p.x - x) / width, (p.y - y) / height);\n}\n\n/** Combined bounds of paths. */\nfunction pathsBounds(paths: readonly BezierPath[]): VectorBounds | null {\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const p of paths) {\n\t\tconst b = bezierPathBounds(p);\n\t\tif (!b) continue;\n\t\tminX = Math.min(minX, b.minX);\n\t\tminY = Math.min(minY, b.minY);\n\t\tmaxX = Math.max(maxX, b.maxX);\n\t\tmaxY = Math.max(maxY, b.maxY);\n\t}\n\treturn minX <= maxX ? { x: minX, y: minY, width: maxX - minX, height: maxY - minY } : null;\n}\n\n/** The paths through an envelope (each mapped adaptively, within a tolerance relative to their size). */\nfunction envelopePaths(paths: BezierPath[], params: VectorEnvelopeParams): BezierPath[] {\n\tconst bounds = pathsBounds(paths);\n\tconst f = vectorEnvelopeMap(params, bounds);\n\tif (!f || !bounds) return paths;\n\tconst tol = Math.min(0.25, Math.max(0.01, Math.hypot(bounds.width, bounds.height) / 2000));\n\treturn paths.map((p) => (p.length ? mapBezierPath(p, f, tol) : p));\n}\n\nconst isVec2 = (v: unknown): v is Vec2 => !!v && typeof v === 'object' && Number.isFinite((v as Vec2).x) && Number.isFinite((v as Vec2).y);\n\n/**\n * Effect parameters with defaults filled in and invalid values dropped (unknown keys go too).\n * Returns null for an unknown type.\n */\nexport function normalizeVectorEffectParams<K extends VectorEffectType>(type: K, params: Record<string, unknown> | null | undefined, base?: VectorEffectParamsMap[K]): VectorEffectParamsMap[K] | null {\n\tconst defaults = VECTOR_EFFECT_DEFAULTS[type] as Record<string, unknown> | undefined;\n\tif (!defaults) return null;\n\tconst out: Record<string, unknown> = { ...defaults, ...((base as Record<string, unknown> | undefined) ?? {}) };\n\tfor (const spec of VECTOR_EFFECT_PARAM_SPECS[type]) {\n\t\tconst v = params?.[spec.key];\n\t\tif (v === undefined) continue;\n\t\tif (spec.kind === 'number' && typeof v === 'number' && Number.isFinite(v)) out[spec.key] = Math.min(spec.max ?? Infinity, Math.max(spec.min ?? -Infinity, v));\n\t\telse if (spec.kind === 'boolean' && typeof v === 'boolean') out[spec.key] = v;\n\t\telse if (spec.kind === 'vec2' && isVec2(v)) out[spec.key] = { x: v.x, y: v.y };\n\t\telse if (spec.kind === 'select' && spec.options.some((o) => o.value === v)) out[spec.key] = v;\n\t}\n\tif (type === 'envelope') {\n\t\tconst o = out as unknown as VectorEnvelopeParams;\n\t\to.rows = Math.round(o.rows);\n\t\to.columns = Math.round(o.columns);\n\t\tconst given = params?.mesh;\n\t\tlet mesh: VectorWarpMesh | null = given !== undefined ? (isValidVectorMesh(given) ? cloneMesh(given) : null) : o.mesh;\n\t\tif (o.style !== 'mesh') mesh = null;\n\t\tif (mesh && given !== undefined) {\n\t\t\to.columns = mesh.us.length - 1;\n\t\t\to.rows = mesh.vs.length - 1;\n\t\t} else if (mesh) mesh = resizeVectorMesh(mesh, o.columns, o.rows);\n\t\to.mesh = mesh;\n\t}\n\treturn out as VectorEffectParamsMap[K];\n}\n\n/** A new effect of `type` (defaults merged with `params`), or null for an unknown type. */\nexport function createVectorEffect<K extends VectorEffectType>(type: K, id: string, params?: Partial<VectorEffectParamsMap[K]> | Record<string, unknown>): Extract<VectorEffect, { type: K }> | null {\n\tconst p = normalizeVectorEffectParams(type, params as Record<string, unknown>);\n\treturn p ? ({ id, type, params: p } as unknown as Extract<VectorEffect, { type: K }>) : null;\n}\n\nconst isEnabled = (e: VectorEffect) => e.enabled !== false && !!VECTOR_EFFECT_DEFAULTS[e.type];\n\n/** The node's effects that take part (enabled, of a known type). */\nexport function activeVectorEffects(node: VectorNode): VectorEffect[] {\n\treturn node.effects?.filter(isEnabled) ?? [];\n}\n\n/** Whether the node itself draws differently from its stored geometry (effects or a live boolean). */\nexport function hasOwnVectorAppearance(node: VectorNode): boolean {\n\treturn (node.type === 'group' && !!node.liveBoolean) || !!node.effects?.some(isEnabled);\n}\n\nconst liveCache = new WeakMap<VectorNode, boolean>();\n\n/** Whether the node or anything inside it has a live appearance. */\nexport function hasVectorAppearance(node: VectorNode): boolean {\n\tlet v = liveCache.get(node);\n\tif (v === undefined) {\n\t\tv = hasOwnVectorAppearance(node) || (node.type === 'group' && node.children.some(hasVectorAppearance));\n\t\tliveCache.set(node, v);\n\t}\n\treturn v;\n}\n\n/** A live boolean group (its children are operands). */\nexport function isVectorLiveBoolean(node: VectorNode | null | undefined): node is VectorGroupNode & { liveBoolean: VectorBooleanOperation } {\n\treturn node?.type === 'group' && !!node.liveBoolean;\n}\n\n/** How operand `child` of a live boolean group combines with the operands below it. */\nexport function vectorOperandOperation(group: VectorGroupNode, child: VectorNode): VectorBooleanOperation {\n\treturn child.booleanOperation ?? group.liveBoolean ?? 'union';\n}\n\n// ---------------------------------------------------------------------------\n// Live boolean fold\n// ---------------------------------------------------------------------------\n\nconst PATH_OPS = { union: 'union', subtract: 'difference', intersect: 'intersection', exclude: 'xor' } as const;\n\n/** A bounded map of fold prefixes; the least recently used half goes when it is full. */\nclass FoldCache {\n\tprivate map = new Map<string, BezierPath>();\n\tconstructor(private readonly cap: number) {}\n\tget(key: string): BezierPath | undefined {\n\t\tconst hit = this.map.get(key);\n\t\tif (hit) {\n\t\t\tthis.map.delete(key);\n\t\t\tthis.map.set(key, hit);\n\t\t}\n\t\treturn hit;\n\t}\n\tset(key: string, path: BezierPath) {\n\t\tif (this.map.size >= this.cap) {\n\t\t\tlet drop = Math.floor(this.map.size / 2);\n\t\t\tfor (const k of this.map.keys()) {\n\t\t\t\tif (drop-- <= 0) break;\n\t\t\t\tthis.map.delete(k);\n\t\t\t}\n\t\t}\n\t\tthis.map.set(key, path);\n\t}\n\tclear() {\n\t\tthis.map.clear();\n\t}\n}\n\nconst foldCache = new FoldCache(4096);\nlet foldSteps = 0;\n\n/** How many boolean steps (one operand combined with the result below it) live booleans have computed: what a cache miss costs. */\nexport function vectorLiveBooleanSteps(): number {\n\treturn foldSteps;\n}\n\n/** Empties the live boolean prefix cache. */\nexport function clearVectorLiveBooleanCache(): void {\n\tfoldCache.clear();\n}\n\n/** A rolling hash of operands: two 32-bit FNV-1a lanes over every number's bits. */\nclass RollingHash {\n\tprivate a = 0x811c9dc5;\n\tprivate b = 0x01000193 ^ 0x5bd1e995;\n\tprivate static view = new DataView(new ArrayBuffer(8));\n\tbyte(v: number) {\n\t\tthis.a = Math.imul(this.a ^ (v & 0xff), 0x01000193);\n\t\tthis.b = Math.imul(this.b ^ (v & 0xff), 0x5bd1e995) ^ (this.b >>> 15);\n\t}\n\tnum(n: number) {\n\t\tRollingHash.view.setFloat64(0, n);\n\t\tfor (let i = 0; i < 8; i++) this.byte(RollingHash.view.getUint8(i));\n\t}\n\tpath(p: BezierPath) {\n\t\tfor (const c of p) {\n\t\t\tthis.byte(c.type.charCodeAt(0));\n\t\t\tif (c.type === 'Z') continue;\n\t\t\tif (c.type === 'C') {\n\t\t\t\tthis.num(c.x1);\n\t\t\t\tthis.num(c.y1);\n\t\t\t\tthis.num(c.x2);\n\t\t\t\tthis.num(c.y2);\n\t\t\t} else if (c.type === 'Q') {\n\t\t\t\tthis.num(c.x1);\n\t\t\t\tthis.num(c.y1);\n\t\t\t}\n\t\t\tthis.num(c.x);\n\t\t\tthis.num(c.y);\n\t\t}\n\t}\n\tkey(): string {\n\t\treturn `${(this.a >>> 0).toString(36)}.${(this.b >>> 0).toString(36)}`;\n\t}\n}\n\n/** One operand of a live boolean: its outline (group space), fill rule and operation (unused for the bottom one). */\nexport interface VectorBooleanOperand {\n\tpath: BezierPath;\n\tfillRule: VectorFillRule;\n\toperation: VectorBooleanOperation;\n}\n\n/**\n * The result of folding `operands` bottom to top, reusing the longest cached prefix: only the\n * operands above it are combined. Invalid geometry gives an empty path.\n */\nexport function foldVectorBooleanOperands(operands: readonly VectorBooleanOperand[]): BezierPath {\n\tif (!operands.length) return [];\n\tif (operands.length === 1) return operands[0].path;\n\tconst h = new RollingHash();\n\tconst keys = operands.map((o, i) => {\n\t\tif (i > 0) h.byte(o.operation.charCodeAt(0) + o.operation.length);\n\t\th.byte(o.fillRule === 'evenodd' ? 1 : 0);\n\t\th.path(o.path);\n\t\th.byte(9);\n\t\treturn h.key();\n\t});\n\tlet start = 0;\n\tlet acc: BezierPath = operands[0].path;\n\tfor (let i = keys.length - 1; i >= 1; i--) {\n\t\tconst hit = foldCache.get(keys[i]);\n\t\tif (hit) {\n\t\t\tif (i === keys.length - 1) return hit;\n\t\t\tstart = i;\n\t\t\tacc = hit;\n\t\t\tbreak;\n\t\t}\n\t}\n\tlet accRule: VectorFillRule = start === 0 ? operands[0].fillRule : 'nonzero';\n\tfor (let i = start + 1; i < operands.length; i++) {\n\t\tconst o = operands[i];\n\t\tfoldSteps++;\n\t\tconst r = pathBoolean(PATH_OPS[o.operation] ?? 'union', [\n\t\t\t{ path: acc, fillRule: accRule },\n\t\t\t{ path: o.path, fillRule: o.fillRule }\n\t\t]);\n\t\tif (r.error) return [];\n\t\tacc = r.path;\n\t\taccRule = 'nonzero';\n\t\tfoldCache.set(keys[i], acc);\n\t}\n\treturn acc;\n}\n\n// ---------------------------------------------------------------------------\n// The effect stack\n// ---------------------------------------------------------------------------\n\nconst compareOrder = (a: readonly number[], b: readonly number[]) => {\n\tconst n = Math.min(a.length, b.length);\n\tfor (let i = 0; i < n; i++) if (a[i] !== b[i]) return a[i] - b[i];\n\treturn a.length - b.length;\n};\n\nconst add = (a: Vec2, b: Vec2 | undefined): Vec2 => ({ x: a.x + (b?.x ?? 0), y: a.y + (b?.y ?? 0) });\n\n/** Every path operator maps the shapes' paths one to one. */\nconst mapPaths = (shapes: VectorAppearanceShape[], fn: (paths: BezierPath[]) => BezierPath[]): VectorAppearanceShape[] => {\n\tconst out = fn(shapes.map((s) => s.path));\n\treturn shapes.map((s, i) => ({ ...s, path: out[i] ?? [] }));\n};\n\n/** Applies an effect stack to shapes, top to bottom (disabled effects skipped). */\nexport function applyVectorEffects(shapes: readonly VectorAppearanceShape[], effects: readonly VectorEffect[] | null | undefined): VectorAppearanceShape[] {\n\tlet list = shapes.slice();\n\tfor (const e of effects ?? []) {\n\t\tif (!isEnabled(e) || !list.length) continue;\n\t\tswitch (e.type) {\n\t\t\tcase 'trim': {\n\t\t\t\tconst { start, end, offset, mode } = e.params;\n\t\t\t\tlist = mapPaths(list, (p) => (mode === 'individual' ? trimPathsIndividually(p, start, end, offset) : trimPaths(p, start, end, offset)));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'roundCorners':\n\t\t\t\tlist = mapPaths(list, (p) => roundCornerPaths(p, e.params.radius));\n\t\t\t\tbreak;\n\t\t\tcase 'puckerBloat':\n\t\t\t\tlist = mapPaths(list, (p) => puckerBloatPaths(p, e.params.amount));\n\t\t\t\tbreak;\n\t\t\tcase 'twist':\n\t\t\t\tlist = mapPaths(list, (p) => twistPaths(p, e.params.angle, add(pathsCenter(p), e.params.center)));\n\t\t\t\tbreak;\n\t\t\tcase 'zigzag':\n\t\t\t\tlist = mapPaths(list, (p) => zigzagPaths(p, e.params.size, e.params.ridges, e.params.smooth));\n\t\t\t\tbreak;\n\t\t\tcase 'wiggle':\n\t\t\t\tlist = mapPaths(list, (p) => wigglePaths(p, e.params));\n\t\t\t\tbreak;\n\t\t\tcase 'offset': {\n\t\t\t\tconst o = e.params;\n\t\t\t\tlist = mapPaths(list, (p) => offsetPaths(p, o.amount, o.join, o.miterLimit, o.copies, o.copyOffset));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'merge': {\n\t\t\t\tconst first = list[0];\n\t\t\t\tlist = [{ ...first, path: mergePaths(list.map((s) => s.path), e.params.mode) }];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'envelope': {\n\t\t\t\tconst params = e.params;\n\t\t\t\tlist = mapPaths(list, (p) => envelopePaths(p, params));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'repeater': {\n\t\t\t\tconst r = e.params;\n\t\t\t\tconst copies = repeatPaths(\n\t\t\t\t\tlist.map((s) => s.path),\n\t\t\t\t\tr,\n\t\t\t\t\tadd(pathsCenter(list.map((s) => s.path)), r.anchor)\n\t\t\t\t);\n\t\t\t\tconst n = copies.length;\n\t\t\t\tconst next: VectorAppearanceShape[] = [];\n\t\t\t\tfor (const { copy, paths } of copies) {\n\t\t\t\t\t// Below: each copy draws under the one before it (the original on top).\n\t\t\t\t\tconst rank = r.composite === 'above' ? copy.index : n - 1 - copy.index;\n\t\t\t\t\tlist.forEach((s, i) => next.push({ source: s.source, path: paths[i], opacity: s.opacity * copy.opacity, order: [rank, ...s.order] }));\n\t\t\t\t}\n\t\t\t\tlist = next;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn list;\n}\n\n/** The shapes of a child in its parent's space (hidden children have none), ordered by its index. */\nexport function vectorChildAppearanceShapes(child: VectorNode, index: number): VectorAppearanceShape[] {\n\tif (child.visible === false) return [];\n\tconst m = child.transform;\n\tconst k = child.opacity ?? 1;\n\treturn vectorAppearanceShapes(child).map((s) => ({\n\t\tsource: s.source,\n\t\tpath: m && !isIdentityMatrix(m) ? transformBezierPath(s.path, m) : s.path,\n\t\topacity: s.opacity * k,\n\t\torder: [index, ...s.order]\n\t}));\n}\n\n/** A live boolean group's result: its operands folded, in the bottom operand's style. */\nfunction liveBooleanShapes(group: VectorGroupNode): VectorAppearanceShape[] {\n\tconst operands: VectorBooleanOperand[] = [];\n\tlet source: VectorPathNode | null = null;\n\tgroup.children.forEach((child, i) => {\n\t\tconst shapes = vectorChildAppearanceShapes(child, i);\n\t\tif (!shapes.length) return;\n\t\tsource ??= shapes[0].source;\n\t\tconst path =\n\t\t\tshapes.length === 1 ? shapes[0].path : pathBoolean('union', shapes.map((s) => ({ path: s.path, fillRule: s.source.fillRule ?? 'nonzero' }))).path;\n\t\tconst fillRule: VectorFillRule = shapes.length === 1 ? (shapes[0].source.fillRule ?? 'nonzero') : 'nonzero';\n\t\toperands.push({ path, fillRule, operation: vectorOperandOperation(group, child) });\n\t});\n\tif (!source) return [];\n\treturn [{ source, path: foldVectorBooleanOperands(operands), opacity: 1, order: [0] }];\n}\n\nconst shapeCache = new WeakMap<VectorNode, VectorAppearanceShape[]>();\n\n/** What a node's effect stack starts from: its own outline, its live boolean result or its children's shapes. */\nfunction baseShapes(node: VectorNode): VectorAppearanceShape[] {\n\tif (node.type === 'path') return [{ source: node, path: subpathsToBezier(node.subpaths), opacity: 1, order: [] }];\n\tif (node.liveBoolean) return liveBooleanShapes(node);\n\t// A mask group's effects act on its contents (the mask stays as it is).\n\tif (isVectorMaskGroup(node)) return node.children.flatMap((c, i) => (i === 0 ? [] : vectorChildAppearanceShapes(c, i)));\n\treturn node.children.flatMap((c, i) => vectorChildAppearanceShapes(c, i));\n}\n\n/** The paths entering effect `effectId` of the node's stack (those above it applied), in the node's own space. */\nexport function vectorEffectInputPaths(node: VectorNode, effectId: string): BezierPath[] | null {\n\tconst list = node.effects ?? [];\n\tconst i = list.findIndex((e) => e.id === effectId);\n\tif (i < 0) return null;\n\treturn applyVectorEffects(baseShapes(node), list.slice(0, i)).map((s) => s.path);\n}\n\n/** The box effect `effectId` (an envelope) bends: the combined bounds of the paths entering it, in the node's own space. */\nexport function vectorEffectInputBounds(node: VectorNode, effectId: string): VectorBounds | null {\n\tconst paths = vectorEffectInputPaths(node, effectId);\n\treturn paths ? pathsBounds(paths) : null;\n}\n\n/** The node's first enabled envelope effect, or null. */\nexport function vectorNodeEnvelope(node: VectorNode | null | undefined): Extract<VectorEffect, { type: 'envelope' }> | null {\n\treturn (node?.effects?.find((e) => e.type === 'envelope' && e.enabled !== false) as Extract<VectorEffect, { type: 'envelope' }> | undefined) ?? null;\n}\n\n/**\n * The editable mesh of a node's envelope (its own mesh, or the preset or flat mesh it stands for)\n * with the box it bends, in the node's own space; null without an envelope or contents.\n */\nexport function vectorEnvelopeFrame(node: VectorNode, effectId?: string): { effectId: string; bounds: VectorBounds; mesh: VectorWarpMesh } | null {\n\tconst e = effectId ? node.effects?.find((x) => x.id === effectId) : vectorNodeEnvelope(node);\n\tif (!e || e.type !== 'envelope') return null;\n\tconst bounds = vectorEffectInputBounds(node, e.id);\n\tif (!bounds || !(bounds.width > 0 && bounds.height > 0)) return null;\n\treturn { effectId: e.id, bounds, mesh: vectorEnvelopeMesh(e.params, bounds) };\n}\n\n/**\n * What a node draws as: shapes in its own space (its `transform` and opacity not applied),\n * bottom to top, with its effects and those of everything inside it applied. Cached per node\n * object.\n */\nexport function vectorAppearanceShapes(node: VectorNode): VectorAppearanceShape[] {\n\tconst hit = shapeCache.get(node);\n\tif (hit) return hit;\n\t// A mask group draws its contents (effects applied) clipped by its mask.\n\tconst out = (isVectorMaskGroup(node) ? vectorMaskedShapes(node) : applyVectorEffects(baseShapes(node), node.effects)).sort((a, b) => compareOrder(a.order, b.order));\n\tshapeCache.set(node, out);\n\treturn out;\n}\n\nconst outlineCache = new WeakMap<VectorNode, BezierPath>();\n\n/** Everything a node draws as one path in its own space (live corners cut, effects applied). */\nexport function vectorAppearanceOutline(node: VectorNode): BezierPath {\n\tlet hit = outlineCache.get(node);\n\tif (!hit) {\n\t\thit = node.type === 'path' && !hasOwnVectorAppearance(node) ? subpathsToBezier(node.subpaths) : vectorAppearanceShapes(node).flatMap((s) => s.path);\n\t\toutlineCache.set(node, hit);\n\t}\n\treturn hit;\n}\n\n// ---------------------------------------------------------------------------\n// Expanded nodes\n// ---------------------------------------------------------------------------\n\n/** A shape as a plain path node in `source`'s style. */\nfunction shapeNode(shape: VectorAppearanceShape, id: string): VectorPathNode {\n\tconst {\n\t\tid: _id,\n\t\ttype: _type,\n\t\tsubpaths: _s,\n\t\ttransform: _t,\n\t\teffects: _e,\n\t\tbooleanOperation: _b,\n\t\tblendStep: _bs,\n\t\tlocked: _l,\n\t\tvisible: _v,\n\t\topacity: _o,\n\t\t// The node's own blend mode and filters stay on the node, not on each shape it draws.\n\t\tblendMode: _bm,\n\t\tfilters: _fl,\n\t\t...style\n\t} = shape.source;\n\treturn { ...style, id, type: 'path', subpaths: bezierToSubpaths(shape.path), ...(shape.opacity < 1 ? { opacity: shape.opacity } : {}) };\n}\n\n/** The node's own placement and state, kept by its expanded form. */\nfunction placement(node: VectorNode) {\n\treturn {\n\t\t...(node.name !== undefined ? { name: node.name } : {}),\n\t\t...(node.transform ? { transform: node.transform } : {}),\n\t\t...(node.visible === false ? { visible: false } : {}),\n\t\t...(node.locked ? { locked: true } : {}),\n\t\t...(node.blendStep ? { blendStep: true } : {}),\n\t\t...(node.booleanOperation ? { booleanOperation: node.booleanOperation } : {}),\n\t\t...(node.blendMode ? { blendMode: node.blendMode } : {}),\n\t\t...(node.filters?.length ? { filters: node.filters } : {})\n\t};\n}\n\n/** What a node draws as, one plain path per shape (ids `<id>~fx<n>`, opacity relative to the node). */\nexport function vectorAppearanceNodes(node: VectorNode): VectorPathNode[] {\n\treturn vectorAppearanceShapes(node).map((s, k) => shapeNode(s, `${node.id}~fx${k}`));\n}\n\nconst expandCache = new WeakMap<VectorNode, VectorNode>();\n\n/**\n * The node as plain geometry: path effects and live booleans evaluated into paths (the node\n * itself when nothing inside it is live). A path keeps its id when it draws as one shape;\n * otherwise, like any live group, it becomes a group with that id holding one path per shape\n * (ids `<id>~fx<n>`). Groups without effects of their own keep their structure. Cached per node.\n */\nexport function expandVectorAppearance(node: VectorNode): VectorNode {\n\tif (!hasVectorAppearance(node)) return node;\n\tconst hit = expandCache.get(node);\n\tif (hit) return hit;\n\tlet out: VectorNode;\n\tif (!hasOwnVectorAppearance(node)) {\n\t\tconst group = node as VectorGroupNode;\n\t\tout = { ...group, children: group.children.map(expandVectorAppearance) };\n\t} else if (isVectorMaskGroup(node)) {\n\t\t// A mask group keeps its mask; its effects act on the contents, wrapped in a group of their own.\n\t\tconst { effects, ...group } = node;\n\t\tconst [mask, ...contents] = node.children;\n\t\tconst wrapper: VectorGroupNode = { id: `${node.id}~content`, type: 'group', effects, children: contents };\n\t\tout = { ...group, children: [expandVectorAppearance(mask), ...(contents.length ? [expandVectorAppearance(wrapper)] : [])] };\n\t} else {\n\t\tconst shapes = vectorAppearanceShapes(node);\n\t\tif (node.type === 'path' && shapes.length === 1) {\n\t\t\tconst one = shapes[0];\n\t\t\tconst { effects: _e, opacity: _o, ...rest } = node;\n\t\t\tconst opacity = (node.opacity ?? 1) * one.opacity;\n\t\t\tout = { ...rest, subpaths: bezierToSubpaths(one.path), ...(opacity < 1 ? { opacity } : {}) };\n\t\t} else {\n\t\t\tout = {\n\t\t\t\tid: node.id,\n\t\t\t\ttype: 'group',\n\t\t\t\t...placement(node),\n\t\t\t\t...(node.opacity !== undefined ? { opacity: node.opacity } : {}),\n\t\t\t\tchildren: vectorAppearanceNodes(node)\n\t\t\t};\n\t\t}\n\t}\n\texpandCache.set(node, out);\n\treturn out;\n}\n\n/** Every node of a list expanded (the same array when nothing in it is live). */\nexport function expandVectorAppearances(nodes: readonly VectorNode[]): VectorNode[] {\n\tlet changed = false;\n\tconst out = nodes.map((n) => {\n\t\tconst e = expandVectorAppearance(n);\n\t\tif (e !== n) changed = true;\n\t\treturn e;\n\t});\n\treturn changed ? out : (nodes as VectorNode[]);\n}\n\n/**\n * Reads effects from untrusted data (JSON, agent input): unknown types and malformed entries are\n * dropped, parameters normalized, ids made unique.\n */\nexport function sanitizeVectorEffects(value: unknown): VectorEffect[] {\n\tif (!Array.isArray(value)) return [];\n\tconst out: VectorEffect[] = [];\n\tconst used = new Set<string>();\n\tfor (const raw of value) {\n\t\tif (!raw || typeof raw !== 'object') continue;\n\t\tconst r = raw as Record<string, unknown>;\n\t\tconst type = r.type as VectorEffectType;\n\t\tif (typeof type !== 'string' || !VECTOR_EFFECT_DEFAULTS[type]) continue;\n\t\tlet id = typeof r.id === 'string' && r.id && !used.has(r.id) ? r.id : '';\n\t\tfor (let k = out.length + 1; !id; k++) if (!used.has(`fx${k}`)) id = `fx${k}`;\n\t\tused.add(id);\n\t\tconst effect = createVectorEffect(type, id, (r.params as Record<string, unknown>) ?? {})!;\n\t\tout.push(r.enabled === false ? { ...effect, enabled: false } : effect);\n\t}\n\treturn out;\n}\n\n/**\n * Effect parameters carried through a linear map baked into a node's geometry (scaling or\n * flipping it): offsets and positions move with it, angles turn the other way when it mirrors.\n * Lengths such as sizes and radii are kept, like stroke widths.\n */\nexport function transformVectorEffects(effects: readonly VectorEffect[], m: AffineMatrix, node?: VectorNode): VectorEffect[] {\n\tconst lin = (v: Vec2): Vec2 => ({ x: m[0] * v.x + m[2] * v.y, y: m[1] * v.x + m[3] * v.y });\n\tconst full = (v: Vec2): Vec2 => ({ x: m[0] * v.x + m[2] * v.y + m[4], y: m[1] * v.x + m[3] * v.y + m[5] });\n\tconst mirror = m[0] * m[3] - m[1] * m[2] < 0 ? -1 : 1;\n\t// A preset is laid over the box of what it bends, so it follows a move or a scale by itself; a\n\t// skew or a mirror turns it into a mesh, which maps like the geometry.\n\tconst axisScale = Math.abs(m[1]) < 1e-12 && Math.abs(m[2]) < 1e-12 && m[0] > 0 && m[3] > 0;\n\treturn effects.map((e) => {\n\t\tif (e.type === 'envelope') {\n\t\t\tif (e.params.style === 'mesh') return e.params.mesh ? { ...e, params: { ...e.params, mesh: mapVectorMeshPoints(e.params.mesh, full) } } : e;\n\t\t\tif (axisScale || !node) return e;\n\t\t\tconst bounds = vectorEffectInputBounds(node, e.id);\n\t\t\tif (!bounds || !vectorEnvelopeMap(e.params, bounds)) return e;\n\t\t\treturn { ...e, params: { ...e.params, style: 'mesh', mesh: mapVectorMeshPoints(vectorEnvelopeMesh(e.params, bounds), full) } };\n\t\t}\n\t\tif (e.type === 'twist') return { ...e, params: { ...e.params, center: lin(e.params.center), angle: e.params.angle * mirror } };\n\t\tif (e.type === 'repeater')\n\t\t\treturn { ...e, params: { ...e.params, position: lin(e.params.position), anchor: lin(e.params.anchor), rotation: e.params.rotation * mirror } };\n\t\treturn e;\n\t});\n}\n\n/**\n * The live groups (live booleans and groups with effects) being edited: those in `ids` and those\n * holding a node in `ids`; mask groups only when they hold one. Their children are hit and outlined as stored instead of as drawn, so\n * each operand can be picked (figcraft `crates/engine/src/booleans.rs`, `result_outline`).\n */\nexport function vectorEnteredGroups(nodes: readonly VectorNode[], ids: readonly string[]): Set<string> {\n\tconst out = new Set<string>();\n\tif (!ids.length) return out;\n\tconst want = new Set(ids);\n\tconst visit = (list: readonly VectorNode[], live: readonly string[]) => {\n\t\tfor (const n of list) {\n\t\t\tconst own = n.type === 'group' && (hasOwnVectorAppearance(n) || isVectorMaskGroup(n));\n\t\t\tif (want.has(n.id)) {\n\t\t\t\tfor (const id of live) out.add(id);\n\t\t\t\t// A selected mask group stays clipped; selecting inside it opens it.\n\t\t\t\tif (own && hasOwnVectorAppearance(n)) out.add(n.id);\n\t\t\t}\n\t\t\tif (n.type === 'group') visit(n.children, own ? [...live, n.id] : live);\n\t\t}\n\t};\n\tvisit(nodes, []);\n\treturn out;\n}\n","/**\n * Vector path geometry: anchors and handles to Bézier paths and back, bounds, hit-testing,\n * affine transforms, segment splitting, anchor-type conversion and shape factories.\n */\nimport { isVectorGradient, transformVectorPaint } from './gradient.js';\nimport type { Point } from '../canvas/types.js';\nimport {\n\tbezierPathBounds,\n\tbezierPathToSvg,\n\ttransformBezierPath,\n\tdistanceToBezierPath,\n\tpointInBezierPath,\n\tsegmentNearest,\n\tsegmentPoint,\n\tsplitSegment,\n\ttoBezierPath,\n\ttype AffineMatrix,\n\ttype BezierPath,\n\ttype BezierPathInput\n} from '../canvas/pathops/bezier.js';\nimport {\n\tIDENTITY_MATRIX,\n\tapplyMatrix,\n\tapplyMatrixLinear,\n\tdecomposeRigid,\n\tinvertMatrix,\n\tisIdentityMatrix,\n\tmatrixRotation,\n\tmultiplyMatrices,\n\trotateMatrix,\n\tscaleMatrix,\n\ttranslateMatrix\n} from '../geom/affine.js';\nimport { expandVectorCorners, setSubpathCorners } from './corners.js';\nimport { hitTestVectorStroke, vectorStrokeOutlinePath } from './stroke.js';\nimport { mirrorWidthProfile } from './width.js';\nimport { hitTestVectorPaintStack } from './paintLayers.js';\nimport { expandVectorAppearance, hasOwnVectorAppearance, transformVectorEffects, vectorAppearanceOutline, vectorAppearanceShapes } from './appearance.js';\nimport { isVectorMaskActive, isVectorMaskGroup, vectorMaskContains } from './mask.js';\nimport type {\n\tVectorAnchor,\n\tVectorAnchorType,\n\tVectorCornerKind,\n\tVectorBounds,\n\tVectorDocument,\n\tVectorGroupNode,\n\tVectorNode,\n\tVectorPaint,\n\tVectorPathNode,\n\tVectorSubpath,\n\tVectorWidthPoint,\n\tVec2\n} from './types.js';\n\nexport type { AffineMatrix };\n\nconst EPS = 1e-9;\n\n// ---------------------------------------------------------------------------\n// Small helpers\n// ---------------------------------------------------------------------------\n\nexport const cloneAnchor = (a: VectorAnchor): VectorAnchor => ({\n\tx: a.x,\n\ty: a.y,\n\t...(a.in ? { in: { x: a.in.x, y: a.in.y } } : {}),\n\t...(a.out ? { out: { x: a.out.x, y: a.out.y } } : {}),\n\t...(a.type && a.type !== 'corner' ? { type: a.type } : {}),\n\t...(a.corner ? { corner: { ...a.corner } } : {})\n});\n\nexport const cloneSubpath = (s: VectorSubpath): VectorSubpath => ({ anchors: s.anchors.map(cloneAnchor), closed: s.closed });\n\n/** A deep copy of a node (ids kept). */\nexport function cloneVectorNode<T extends VectorNode>(node: T): T {\n\treturn JSON.parse(JSON.stringify(node)) as T;\n}\n\nexport function cloneVectorDocument(doc: VectorDocument): VectorDocument {\n\treturn JSON.parse(JSON.stringify(doc)) as VectorDocument;\n}\n\n/** True when the paint draws something (not `null`, `'none'`, `'transparent'` or empty). */\nexport function hasPaint(paint: VectorPaint | undefined): boolean {\n\tif (paint === undefined || paint === null) return false;\n\tif (typeof paint === 'string') {\n\t\tconst p = paint.trim().toLowerCase();\n\t\treturn p !== '' && p !== 'none' && p !== 'transparent';\n\t}\n\treturn true;\n}\n\nconst handleOf = (a: VectorAnchor, side: 'in' | 'out'): Vec2 => a[side] ?? { x: a.x, y: a.y };\nconst isOnAnchor = (a: VectorAnchor, h: Vec2 | null | undefined) => !h || (Math.abs(h.x - a.x) < EPS && Math.abs(h.y - a.y) < EPS);\n\n// ---------------------------------------------------------------------------\n// Anchors ↔ Bézier\n// ---------------------------------------------------------------------------\n\n/** Number of segments of a subpath (a closed one has a segment from the last anchor back to the first). */\nexport function subpathSegmentCount(sp: VectorSubpath): number {\n\tconst n = sp.anchors.length;\n\tif (n < 2) return 0;\n\treturn sp.closed ? n : n - 1;\n}\n\n/** Control points of segment `i` of a subpath: 2 for a line, 4 for a cubic. */\nexport function subpathSegment(sp: VectorSubpath, i: number): Point[] {\n\tconst a = sp.anchors[i];\n\tconst b = sp.anchors[(i + 1) % sp.anchors.length];\n\tif (isOnAnchor(a, a.out) && isOnAnchor(b, b.in)) return [[a.x, a.y], [b.x, b.y]];\n\tconst c1 = handleOf(a, 'out');\n\tconst c2 = handleOf(b, 'in');\n\treturn [[a.x, a.y], [c1.x, c1.y], [c2.x, c2.y], [b.x, b.y]];\n}\n\n/**\n * Absolute `M` / `L` / `C` / `Z` commands of subpaths, with live corners cut\n * (`expandVectorCorners`). Pass `{ corners: false }` for the stored anchors as they are.\n */\nexport function subpathsToBezier(subpaths: readonly VectorSubpath[], options: { corners?: boolean } = {}): BezierPath {\n\tconst out: BezierPath = [];\n\tfor (const sp of options.corners === false ? subpaths : expandVectorCorners(subpaths)) {\n\t\tif (sp.anchors.length === 0) continue;\n\t\tconst first = sp.anchors[0];\n\t\tout.push({ type: 'M', x: first.x, y: first.y });\n\t\tconst count = subpathSegmentCount(sp);\n\t\tfor (let i = 0; i < count; i++) {\n\t\t\tconst seg = subpathSegment(sp, i);\n\t\t\tif (sp.closed && i === count - 1 && seg.length === 2) break; // `Z` draws the closing line.\n\t\t\tif (seg.length === 2) out.push({ type: 'L', x: seg[1][0], y: seg[1][1] });\n\t\t\telse out.push({ type: 'C', x1: seg[1][0], y1: seg[1][1], x2: seg[2][0], y2: seg[2][1], x: seg[3][0], y: seg[3][1] });\n\t\t}\n\t\tif (sp.closed) out.push({ type: 'Z' });\n\t}\n\treturn out;\n}\n\n/** The node's outline as Bézier commands, as drawn (path effects applied). Groups have none. */\nexport function vectorNodeToBezier(node: VectorNode): BezierPath {\n\treturn node.type === 'path' ? vectorAppearanceOutline(node) : [];\n}\n\n/** SVG path data (`d`) of a path node or subpaths. */\nexport function vectorPathData(input: VectorPathNode | readonly VectorSubpath[], precision = 3): string {\n\tconst subpaths = Array.isArray(input) ? (input as readonly VectorSubpath[]) : (input as VectorPathNode).subpaths;\n\treturn bezierPathToSvg(subpathsToBezier(subpaths), { precision });\n}\n\n/** Infers the anchor type from its handles. */\nexport function inferAnchorType(a: VectorAnchor): VectorAnchorType {\n\tif (isOnAnchor(a, a.in) || isOnAnchor(a, a.out)) return 'corner';\n\tconst ix = a.in!.x - a.x;\n\tconst iy = a.in!.y - a.y;\n\tconst ox = a.out!.x - a.x;\n\tconst oy = a.out!.y - a.y;\n\tconst li = Math.hypot(ix, iy);\n\tconst lo = Math.hypot(ox, oy);\n\tconst cross = (ix * oy - iy * ox) / (li * lo);\n\tconst dot = (ix * ox + iy * oy) / (li * lo);\n\tif (Math.abs(cross) > 1e-3 || dot > 0) return 'corner';\n\treturn Math.abs(li - lo) <= 1e-3 * Math.max(li, lo) ? 'symmetric' : 'smooth';\n}\n\n/**\n * Converts SVG path data or Bézier commands to editable subpaths. Quadratics become cubics,\n * arcs become cubics, a closing anchor that repeats the first one is merged into it, and\n * each anchor's type is inferred from its handles.\n */\nexport function bezierToSubpaths(input: BezierPathInput): VectorSubpath[] {\n\tconst path = toBezierPath(input);\n\tconst out: VectorSubpath[] = [];\n\tlet current: VectorSubpath | null = null;\n\tconst last = () => current!.anchors[current!.anchors.length - 1];\n\tfor (const cmd of path) {\n\t\tswitch (cmd.type) {\n\t\t\tcase 'M':\n\t\t\t\tcurrent = { anchors: [{ x: cmd.x, y: cmd.y }], closed: false };\n\t\t\t\tout.push(current);\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\tcurrent!.anchors.push({ x: cmd.x, y: cmd.y });\n\t\t\t\tbreak;\n\t\t\tcase 'Q': {\n\t\t\t\tconst p0 = last();\n\t\t\t\tp0.out = { x: p0.x + (2 / 3) * (cmd.x1 - p0.x), y: p0.y + (2 / 3) * (cmd.y1 - p0.y) };\n\t\t\t\tcurrent!.anchors.push({ x: cmd.x, y: cmd.y, in: { x: cmd.x + (2 / 3) * (cmd.x1 - cmd.x), y: cmd.y + (2 / 3) * (cmd.y1 - cmd.y) } });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'C':\n\t\t\t\tlast().out = { x: cmd.x1, y: cmd.y1 };\n\t\t\t\tcurrent!.anchors.push({ x: cmd.x, y: cmd.y, in: { x: cmd.x2, y: cmd.y2 } });\n\t\t\t\tbreak;\n\t\t\tcase 'Z': {\n\t\t\t\tif (!current) break;\n\t\t\t\tcurrent.closed = true;\n\t\t\t\tconst anchors = current.anchors;\n\t\t\t\tif (anchors.length > 1) {\n\t\t\t\t\tconst first = anchors[0];\n\t\t\t\t\tconst end = anchors[anchors.length - 1];\n\t\t\t\t\tif (Math.abs(end.x - first.x) < 1e-7 && Math.abs(end.y - first.y) < 1e-7) {\n\t\t\t\t\t\tif (end.in) first.in = end.in;\n\t\t\t\t\t\tanchors.pop();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcurrent = null;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tfor (const sp of out) {\n\t\tfor (const a of sp.anchors) {\n\t\t\tif (isOnAnchor(a, a.in)) delete a.in;\n\t\t\tif (isOnAnchor(a, a.out)) delete a.out;\n\t\t\tconst type = inferAnchorType(a);\n\t\t\tif (type !== 'corner') a.type = type;\n\t\t}\n\t}\n\treturn out.filter((sp) => sp.anchors.length > 0);\n}\n\n// ---------------------------------------------------------------------------\n// Tree helpers\n// ---------------------------------------------------------------------------\n\n/** Calls `fn` for every node, depth first, parents before children. */\nexport function walkVectorNodes(nodes: readonly VectorNode[], fn: (node: VectorNode, parent: VectorGroupNode | null, depth: number) => void): void {\n\tconst visit = (list: readonly VectorNode[], parent: VectorGroupNode | null, depth: number) => {\n\t\tfor (const node of list) {\n\t\t\tfn(node, parent, depth);\n\t\t\tif (node.type === 'group') visit(node.children, node, depth + 1);\n\t\t}\n\t};\n\tvisit(nodes, null, 0);\n}\n\n/** Finds a node by id anywhere in the tree. */\nexport function findVectorNode(nodes: readonly VectorNode[], id: string): VectorNode | undefined {\n\tfor (const node of nodes) {\n\t\tif (node.id === id) return node;\n\t\tif (node.type === 'group') {\n\t\t\tconst found = findVectorNode(node.children, id);\n\t\t\tif (found) return found;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/** The list that contains node `id` (the root list or a group's children) and its index there. */\nexport function locateVectorNode(nodes: VectorNode[], id: string): { list: VectorNode[]; index: number; parent: VectorGroupNode | null } | null {\n\tconst search = (list: VectorNode[], parent: VectorGroupNode | null): ReturnType<typeof locateVectorNode> => {\n\t\tfor (let i = 0; i < list.length; i++) {\n\t\t\tconst node = list[i];\n\t\t\tif (node.id === id) return { list, index: i, parent };\n\t\t\tif (node.type === 'group') {\n\t\t\t\tconst found = search(node.children, node);\n\t\t\t\tif (found) return found;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t};\n\treturn search(nodes, null);\n}\n\n/** Every path node in the subtree (a path node itself, or a group's descendants). */\nexport function collectPathNodes(node: VectorNode): VectorPathNode[] {\n\tif (node.type === 'path') return [node];\n\tconst out: VectorPathNode[] = [];\n\twalkVectorNodes(node.children, (n) => {\n\t\tif (n.type === 'path') out.push(n);\n\t});\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Bounds\n// ---------------------------------------------------------------------------\n\n/** The node's local → parent matrix (identity when it has no `transform`). */\nexport function nodeMatrix(node: VectorNode): AffineMatrix {\n\treturn node.transform ?? IDENTITY_MATRIX;\n}\n\n/**\n * A path node's outline in the space `parent` maps to (default: its parent's space), as drawn:\n * live corners cut and path effects applied (`{ effects: false }`: the stored path only).\n */\nexport function vectorPathWorldBezier(node: VectorPathNode, parent: AffineMatrix = IDENTITY_MATRIX, options: { effects?: boolean } = {}): BezierPath {\n\tconst m = multiplyMatrices(nodeMatrix(node), parent);\n\tconst path = options.effects === false || !hasOwnVectorAppearance(node) ? subpathsToBezier(node.subpaths) : vectorAppearanceOutline(node);\n\treturn isIdentityMatrix(m) ? path : transformBezierPath(path, m);\n}\n\n/**\n * Geometric bounds of a node (stroke width not included) in the space `parent` maps to —\n * by default the node's parent space, which for top-level nodes is the document. Null when\n * the node has no points. A mask group is bounded by its masked result.\n */\nexport function vectorNodeBounds(node: VectorNode, parent: AffineMatrix = IDENTITY_MATRIX): VectorBounds | null {\n\tif (isVectorMaskActive(node)) {\n\t\tconst m = multiplyMatrices(nodeMatrix(node), parent);\n\t\tconst b = bezierPathBounds(vectorAppearanceShapes(node).flatMap((s) => (isIdentityMatrix(m) ? s.path : transformBezierPath(s.path, m))));\n\t\treturn b ? { x: b.minX, y: b.minY, width: b.maxX - b.minX, height: b.maxY - b.minY } : null;\n\t}\n\tnode = expandVectorAppearance(node);\n\tif (node.type === 'group') {\n\t\tconst m = multiplyMatrices(nodeMatrix(node), parent);\n\t\treturn unionBounds(node.children.map((c) => vectorNodeBounds(c, m)));\n\t}\n\tconst b = bezierPathBounds(vectorPathWorldBezier(node, parent));\n\tif (!b) return null;\n\treturn { x: b.minX, y: b.minY, width: b.maxX - b.minX, height: b.maxY - b.minY };\n}\n\n/** The smallest box around all non-null boxes, or null. */\nexport function unionBounds(list: readonly (VectorBounds | null | undefined)[]): VectorBounds | null {\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const b of list) {\n\t\tif (!b) continue;\n\t\tminX = Math.min(minX, b.x);\n\t\tminY = Math.min(minY, b.y);\n\t\tmaxX = Math.max(maxX, b.x + b.width);\n\t\tmaxY = Math.max(maxY, b.y + b.height);\n\t}\n\tif (minX === Infinity) return null;\n\treturn { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n/** True when two boxes overlap (touching counts). */\nexport function boundsIntersect(a: VectorBounds, b: VectorBounds): boolean {\n\treturn a.x <= b.x + b.width && b.x <= a.x + a.width && a.y <= b.y + b.height && b.y <= a.y + a.height;\n}\n\n/** A box from two corners in any order. */\nexport function boundsFromPoints(a: Vec2, b: Vec2): VectorBounds {\n\treturn { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), width: Math.abs(b.x - a.x), height: Math.abs(b.y - a.y) };\n}\n\n// ---------------------------------------------------------------------------\n// Hit-testing\n// ---------------------------------------------------------------------------\n\n/** Options of `hitTestVectorNode`. */\nexport interface VectorHitTestOptions {\n\t/**\n\t * Live groups (live booleans or groups with effects) hit by their children as stored rather\n\t * than by what they draw: the groups being edited.\n\t */\n\tentered?: ReadonlySet<string>;\n}\n\n/**\n * Whether `p` hits a node as drawn (path effects and live booleans applied): inside its fill\n * (when it has one), on its stroke (its alignment and arrowheads respected) or within\n * `tolerance` of either. A group is hit when any visible child is; a mask group when a child\n * above the mask is, where the mask lets it through. An entered mask group is hit by its children\n * as stored, the mask by its outline when it clips.\n */\nexport function hitTestVectorNode(node: VectorNode, p: Vec2, tolerance = 0, parent: AffineMatrix = IDENTITY_MATRIX, options: VectorHitTestOptions = {}): boolean {\n\tif (node.visible === false) return false;\n\tif (isVectorMaskGroup(node)) {\n\t\tconst m = multiplyMatrices(nodeMatrix(node), parent);\n\t\tconst entered = !!options.entered?.has(node.id);\n\t\tif (!entered && isVectorMaskActive(node) && !vectorMaskContains(node, applyMatrix(invertMatrix(m), p), tolerance)) return false;\n\t\t// With effects, the contents hit as drawn (unless entered).\n\t\tconst shown = !entered && hasOwnVectorAppearance(node) ? (expandVectorAppearance(node) as VectorGroupNode).children : node.children;\n\t\tfor (let i = shown.length - 1; i >= 1; i--) if (hitTestVectorNode(shown[i], p, tolerance, m, options)) return true;\n\t\treturn entered && hitTestVectorMaskSource(node, p, tolerance, m, options);\n\t}\n\t// Plain groups recurse as stored, so an entered live group deeper down still hits by its children.\n\tif (!(node.type === 'group' && (!hasOwnVectorAppearance(node) || options.entered?.has(node.id)))) node = expandVectorAppearance(node);\n\tif (node.type === 'group') {\n\t\tconst m = multiplyMatrices(nodeMatrix(node), parent);\n\t\treturn node.children.some((c) => hitTestVectorNode(c, p, tolerance, m, options));\n\t}\n\tconst path = vectorPathWorldBezier(node, parent);\n\tif (path.length === 0) return false;\n\t// Every drawn fill and stroke layer counts (hidden layers don't).\n\treturn hitTestVectorPaintStack(node, path, p, tolerance);\n}\n\n/** Whether `p` hits an entered mask group's mask: as drawn for an opacity mask, by its outline (any paint) for a clip. */\nfunction hitTestVectorMaskSource(group: VectorGroupNode, p: Vec2, tolerance: number, m: AffineMatrix, options: VectorHitTestOptions): boolean {\n\tconst source = group.children[0];\n\tif (source.visible === false) return false;\n\tif (group.mask?.type !== 'clip') return hitTestVectorNode(source, p, tolerance, m, options);\n\tconst paths = collectPathNodes(source).filter((n) => n.visible !== false);\n\tconst pt: [number, number] = [p.x, p.y];\n\treturn paths.some((n) => {\n\t\tconst world = vectorParentMatrix([source], n.id);\n\t\tconst path = vectorPathWorldBezier(n, multiplyMatrices(world ?? IDENTITY_MATRIX, m));\n\t\treturn pointInBezierPath(path, pt, n.fillRule ?? 'nonzero') || distanceToBezierPath(path, pt) <= tolerance;\n\t});\n}\n\n/**\n * The area a path node's stroke paints (alignment, caps, joins, dashes and arrowheads\n * included), as a filled path in the space `parent` maps to. Empty without a stroke.\n */\nexport function vectorStrokeOutline(node: VectorPathNode, parent: AffineMatrix = IDENTITY_MATRIX): BezierPath {\n\tif (!hasPaint(node.stroke) || !((node.strokeWidth ?? 1) > 0)) return [];\n\treturn vectorStrokeOutlinePath(vectorPathWorldBezier(node, parent), node);\n}\n\n/** The topmost node hit by `p` (skipping hidden and locked ones), or null. */\nexport function hitTestVectorNodes(nodes: readonly VectorNode[], p: Vec2, tolerance = 0, options: VectorHitTestOptions = {}): VectorNode | null {\n\tfor (let i = nodes.length - 1; i >= 0; i--) {\n\t\tconst node = nodes[i];\n\t\tif (node.visible === false || node.locked) continue;\n\t\tif (hitTestVectorNode(node, p, tolerance, IDENTITY_MATRIX, options)) return node;\n\t}\n\treturn null;\n}\n\nexport interface NearestSegment {\n\tsubpath: number;\n\tsegment: number;\n\t/** Curve parameter 0–1 on the segment. */\n\tt: number;\n\tpoint: Vec2;\n\tdistance: number;\n}\n\n/** The closest point on any segment of a path node (`p` and the result are in the node's own space). */\nexport function nearestSegment(node: VectorPathNode, p: Vec2): NearestSegment | null {\n\tlet best: NearestSegment | null = null;\n\tnode.subpaths.forEach((sp, s) => {\n\t\tconst count = subpathSegmentCount(sp);\n\t\tfor (let i = 0; i < count; i++) {\n\t\t\tconst seg = subpathSegment(sp, i);\n\t\t\tconst hit = segmentNearest(seg, [p.x, p.y]);\n\t\t\tif (!best || hit.distance < best.distance) {\n\t\t\t\tconst pt = segmentPoint(seg, hit.t);\n\t\t\t\tbest = { subpath: s, segment: i, t: hit.t, point: { x: pt[0], y: pt[1] }, distance: hit.distance };\n\t\t\t}\n\t\t}\n\t});\n\treturn best;\n}\n\n// ---------------------------------------------------------------------------\n// Affine transforms\n// ---------------------------------------------------------------------------\n\n// The matrix maths is shared with the image editor (`src/geom/affine.ts`).\nexport {\n\tIDENTITY_MATRIX,\n\tmultiplyMatrices,\n\ttranslateMatrix,\n\tscaleMatrix,\n\trotateMatrix,\n\tapplyMatrix,\n\tisIdentityMatrix,\n\tinvertMatrix,\n\tapplyMatrixLinear,\n\tmatrixRotation,\n\tdecomposeRigid\n};\n\nfunction mapAnchor(a: VectorAnchor, m: AffineMatrix): VectorAnchor {\n\tconst p = applyMatrix(m, a);\n\tconst next: VectorAnchor = { x: p.x, y: p.y };\n\tif (a.in) next.in = applyMatrix(m, a.in);\n\tif (a.out) next.out = applyMatrix(m, a.out);\n\tif (a.type) next.type = a.type;\n\tif (a.corner) next.corner = { ...a.corner };\n\treturn next;\n}\n\n/** Every anchor and handle mapped through `m` (the node's own `transform` is untouched). */\nfunction bakeIntoGeometry<T extends VectorNode>(node: T, m: AffineMatrix): T {\n\tif (isIdentityMatrix(m)) return node;\n\tif (node.effects?.length) node = { ...node, effects: transformVectorEffects(node.effects, m, node) } as T;\n\tif (node.type === 'group') {\n\t\tconst group = node as VectorGroupNode;\n\t\tconst children = group.children.map((c) => transformVectorNode(c, m));\n\t\t// A blend's replaced spine lives in the group's space too.\n\t\tconst spine = group.blend?.spine;\n\t\tif (!spine) return { ...group, children } as T;\n\t\tconst moved = { closed: spine.closed, anchors: spine.anchors.map((a) => mapAnchor(a, m)) };\n\t\treturn { ...group, children, blend: { ...group.blend, spine: moved } } as T;\n\t}\n\tconst path = node as VectorPathNode;\n\t// A mirror swaps the sides of a variable-width stroke (the path still runs the same way).\n\tconst flips = m[0] * m[3] - m[1] * m[2] < 0;\n\tconst mirrored = flips && path.strokeWidthProfile?.length;\n\t// Each layer of a stack: its gradient moves with the geometry, its profile mirrors.\n\tconst layer = <L extends { paint: VectorPaint; strokeWidthProfile?: VectorWidthPoint[] | null }>(l: L): L => ({\n\t\t...l,\n\t\t...(isVectorGradient(l.paint) ? { paint: transformVectorPaint(l.paint, m) } : {}),\n\t\t...(flips && l.strokeWidthProfile?.length ? { strokeWidthProfile: mirrorWidthProfile(l.strokeWidthProfile) } : {})\n\t});\n\treturn {\n\t\t...path,\n\t\t...(mirrored ? { strokeWidthProfile: mirrorWidthProfile(path.strokeWidthProfile!) } : {}),\n\t\t// Gradients in the node's own space move with its geometry.\n\t\t...(isVectorGradient(path.fill) ? { fill: transformVectorPaint(path.fill, m) } : {}),\n\t\t...(isVectorGradient(path.stroke) ? { stroke: transformVectorPaint(path.stroke, m) } : {}),\n\t\t...(Array.isArray(path.fills) ? { fills: path.fills.map(layer) } : {}),\n\t\t...(Array.isArray(path.strokes) ? { strokes: path.strokes.map(layer) } : {}),\n\t\tsubpaths: path.subpaths.map((sp) => ({\n\t\t\tclosed: sp.closed,\n\t\t\tanchors: sp.anchors.map((a) => mapAnchor(a, m))\n\t\t}))\n\t} as T;\n}\n\n/**\n * A copy of the node moved by `m`, given in its parent's space. The node's rotation and\n * translation go to its rigid `transform`; scale and skew are baked into its anchors (or its\n * children), so the result draws exactly as `m` says. Stroke widths are kept.\n */\nexport function transformVectorNode<T extends VectorNode>(node: T, m: AffineMatrix): T {\n\tconst total = multiplyMatrices(nodeMatrix(node), m);\n\tconst { rigid, residual } = decomposeRigid(total);\n\tconst { transform: _old, ...rest } = node as VectorNode;\n\tconst baked = bakeIntoGeometry(rest as T, residual);\n\treturn isIdentityMatrix(rigid) ? baked : ({ ...baked, transform: rigid } as T);\n}\n\n/** The node's rotation in degrees (from its `transform` and its ancestors' when `parent` is given). */\nexport function vectorNodeRotation(node: VectorNode, parent: AffineMatrix = IDENTITY_MATRIX): number {\n\treturn matrixRotation(multiplyMatrices(nodeMatrix(node), parent));\n}\n\n/** The local → document matrix of node `id` (ancestors' transforms included), or null. */\nexport function vectorWorldMatrix(nodes: readonly VectorNode[], id: string): AffineMatrix | null {\n\tconst search = (list: readonly VectorNode[], parent: AffineMatrix): AffineMatrix | null => {\n\t\tfor (const n of list) {\n\t\t\tconst m = multiplyMatrices(nodeMatrix(n), parent);\n\t\t\tif (n.id === id) return m;\n\t\t\tif (n.type === 'group') {\n\t\t\t\tconst found = search(n.children, m);\n\t\t\t\tif (found) return found;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t};\n\treturn search(nodes, IDENTITY_MATRIX);\n}\n\n/** The parent space → document matrix of node `id` (identity for top-level nodes), or null. */\nexport function vectorParentMatrix(nodes: readonly VectorNode[], id: string): AffineMatrix | null {\n\tconst node = findVectorNode(nodes, id);\n\tconst world = vectorWorldMatrix(nodes, id);\n\tif (!node || !world) return null;\n\treturn multiplyMatrices(invertMatrix(nodeMatrix(node)), world);\n}\n\n// ---------------------------------------------------------------------------\n// Anchor editing\n// ---------------------------------------------------------------------------\n\n/** Moves an anchor and its handles by (dx, dy). */\nexport function translateAnchor(a: VectorAnchor, dx: number, dy: number): VectorAnchor {\n\tconst next = cloneAnchor(a);\n\tnext.x += dx;\n\tnext.y += dy;\n\tif (next.in) next.in = { x: next.in.x + dx, y: next.in.y + dy };\n\tif (next.out) next.out = { x: next.out.x + dx, y: next.out.y + dy };\n\treturn next;\n}\n\n/**\n * Moves one handle to `pos`, keeping the other in line for `smooth` anchors and mirrored for\n * `symmetric` ones. With `breakHandles` the anchor becomes a `corner` and only this handle moves.\n */\nexport function moveAnchorHandle(a: VectorAnchor, side: 'in' | 'out', pos: Vec2, breakHandles = false): VectorAnchor {\n\tconst next = cloneAnchor(a);\n\tnext[side] = { x: pos.x, y: pos.y };\n\tconst other = side === 'in' ? 'out' : 'in';\n\tconst type = breakHandles ? 'corner' : (a.type ?? 'corner');\n\tif (breakHandles) delete next.type;\n\tconst dx = pos.x - a.x;\n\tconst dy = pos.y - a.y;\n\tconst len = Math.hypot(dx, dy);\n\tif (type === 'symmetric') next[other] = { x: a.x - dx, y: a.y - dy };\n\telse if (type === 'smooth' && len > EPS) {\n\t\tconst o = a[other];\n\t\tconst olen = o ? Math.hypot(o.x - a.x, o.y - a.y) : len;\n\t\tnext[other] = { x: a.x - (dx / len) * olen, y: a.y - (dy / len) * olen };\n\t}\n\tif (isOnAnchor(next, next.in)) delete next.in;\n\tif (isOnAnchor(next, next.out)) delete next.out;\n\treturn next;\n}\n\n/**\n * Converts anchor `index` of a subpath. `corner` removes both handles; `smooth` and\n * `symmetric` keep existing handles in line, or add handles a third of the way to the\n * neighbours along the line between them.\n */\nexport function convertAnchor(sp: VectorSubpath, index: number, type: VectorAnchorType): VectorSubpath {\n\tconst anchors = sp.anchors.map(cloneAnchor);\n\tconst a = anchors[index];\n\tif (!a) return { anchors, closed: sp.closed };\n\tif (type === 'corner') {\n\t\tdelete a.in;\n\t\tdelete a.out;\n\t\tdelete a.type;\n\t\treturn { anchors, closed: sp.closed };\n\t}\n\tconst n = anchors.length;\n\tconst prev = index > 0 ? anchors[index - 1] : sp.closed ? anchors[n - 1] : null;\n\tconst next = index < n - 1 ? anchors[index + 1] : sp.closed ? anchors[0] : null;\n\tlet dir: Vec2 | null = null;\n\tlet lenIn = 0;\n\tlet lenOut = 0;\n\tif (!isOnAnchor(a, a.out)) dir = { x: a.out!.x - a.x, y: a.out!.y - a.y };\n\telse if (!isOnAnchor(a, a.in)) dir = { x: a.x - a.in!.x, y: a.y - a.in!.y };\n\telse if (prev && next) dir = { x: next.x - prev.x, y: next.y - prev.y };\n\telse if (next) dir = { x: next.x - a.x, y: next.y - a.y };\n\telse if (prev) dir = { x: a.x - prev.x, y: a.y - prev.y };\n\tif (!dir || Math.hypot(dir.x, dir.y) < EPS) return { anchors, closed: sp.closed };\n\tconst dl = Math.hypot(dir.x, dir.y);\n\tconst ux = dir.x / dl;\n\tconst uy = dir.y / dl;\n\tlenIn = a.in && !isOnAnchor(a, a.in) ? Math.hypot(a.in.x - a.x, a.in.y - a.y) : prev ? Math.hypot(prev.x - a.x, prev.y - a.y) / 3 : 0;\n\tlenOut = a.out && !isOnAnchor(a, a.out) ? Math.hypot(a.out.x - a.x, a.out.y - a.y) : next ? Math.hypot(next.x - a.x, next.y - a.y) / 3 : 0;\n\tif (type === 'symmetric') lenIn = lenOut = (lenIn + lenOut) / 2 || Math.max(lenIn, lenOut);\n\tif (lenIn > EPS) a.in = { x: a.x - ux * lenIn, y: a.y - uy * lenIn };\n\tif (lenOut > EPS) a.out = { x: a.x + ux * lenOut, y: a.y + uy * lenOut };\n\ta.type = type;\n\treturn { anchors, closed: sp.closed };\n}\n\n/**\n * Inserts an anchor on segment `segment` at parameter `t` without changing the curve's\n * shape (de Casteljau split). Returns the new subpath and the new anchor's index.\n */\nexport function insertAnchor(sp: VectorSubpath, segment: number, t: number): { subpath: VectorSubpath; index: number } {\n\tconst anchors = sp.anchors.map(cloneAnchor);\n\tconst n = anchors.length;\n\tconst ia = segment;\n\tconst ib = (segment + 1) % n;\n\tconst seg = subpathSegment(sp, segment);\n\tconst [left, right] = splitSegment(seg, t);\n\tconst mid = left[left.length - 1];\n\tconst anchor: VectorAnchor = { x: mid[0], y: mid[1] };\n\tif (seg.length === 4) {\n\t\tanchors[ia].out = { x: left[1][0], y: left[1][1] };\n\t\tanchor.in = { x: left[2][0], y: left[2][1] };\n\t\tanchor.out = { x: right[1][0], y: right[1][1] };\n\t\tanchor.type = 'smooth';\n\t\tanchors[ib].in = { x: right[2][0], y: right[2][1] };\n\t\tfor (const k of [ia, ib]) if (isOnAnchor(anchors[k], anchors[k].in)) delete anchors[k].in;\n\t\tfor (const k of [ia, ib]) if (isOnAnchor(anchors[k], anchors[k].out)) delete anchors[k].out;\n\t}\n\tconst index = segment + 1;\n\tanchors.splice(index, 0, anchor);\n\treturn { subpath: { anchors, closed: sp.closed }, index };\n}\n\n/**\n * Removes anchors from a subpath. An open subpath with fewer than 2 anchors left, or a\n * closed one with fewer than 2, is returned with whatever remains (callers drop empty ones).\n */\nexport function removeAnchors(sp: VectorSubpath, indices: readonly number[]): VectorSubpath {\n\tconst drop = new Set(indices);\n\treturn { anchors: sp.anchors.filter((_, i) => !drop.has(i)).map(cloneAnchor), closed: sp.closed };\n}\n\n/** Reverses a subpath's direction (handles swap sides). */\nexport function reverseSubpath(sp: VectorSubpath): VectorSubpath {\n\tconst anchors = sp.anchors\n\t\t.slice()\n\t\t.reverse()\n\t\t.map((a) => {\n\t\t\tconst next: VectorAnchor = { x: a.x, y: a.y };\n\t\t\tif (a.out) next.in = { ...a.out };\n\t\t\tif (a.in) next.out = { ...a.in };\n\t\t\tif (a.type) next.type = a.type;\n\t\t\tif (a.corner) next.corner = { ...a.corner };\n\t\t\treturn next;\n\t\t});\n\treturn { anchors, closed: sp.closed };\n}\n\n// ---------------------------------------------------------------------------\n// Shape factories\n// ---------------------------------------------------------------------------\n\n/** Handle length that makes a cubic approximate a quarter circle. */\nexport const KAPPA = 0.5522847498307936;\n\n/**\n * A rectangle. A `radius` becomes a live corner on each of its four anchors (drawn round, and\n * clamped to half the shorter side, when the path is turned into curves).\n */\nexport function rectSubpath(b: VectorBounds, radius = 0, kind?: VectorCornerKind): VectorSubpath {\n\tconst { x, y, width: w, height: h } = b;\n\tconst sp: VectorSubpath = { anchors: [{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }], closed: true };\n\treturn radius > 0 ? setSubpathCorners(sp, radius, kind) : sp;\n}\n\n/** An ellipse inscribed in a box (four smooth anchors). */\nexport function ellipseSubpath(b: VectorBounds): VectorSubpath {\n\tconst rx = b.width / 2;\n\tconst ry = b.height / 2;\n\tconst cx = b.x + rx;\n\tconst cy = b.y + ry;\n\tconst kx = rx * KAPPA;\n\tconst ky = ry * KAPPA;\n\treturn {\n\t\tclosed: true,\n\t\tanchors: [\n\t\t\t{ x: cx, y: cy - ry, in: { x: cx - kx, y: cy - ry }, out: { x: cx + kx, y: cy - ry }, type: 'symmetric' },\n\t\t\t{ x: cx + rx, y: cy, in: { x: cx + rx, y: cy - ky }, out: { x: cx + rx, y: cy + ky }, type: 'symmetric' },\n\t\t\t{ x: cx, y: cy + ry, in: { x: cx + kx, y: cy + ry }, out: { x: cx - kx, y: cy + ry }, type: 'symmetric' },\n\t\t\t{ x: cx - rx, y: cy, in: { x: cx - rx, y: cy + ky }, out: { x: cx - rx, y: cy - ky }, type: 'symmetric' }\n\t\t]\n\t};\n}\n\n/** A regular polygon inscribed in a box, first vertex at the top. */\nexport function polygonSubpath(b: VectorBounds, sides = 6): VectorSubpath {\n\tconst n = Math.max(3, Math.round(sides));\n\tconst cx = b.x + b.width / 2;\n\tconst cy = b.y + b.height / 2;\n\tconst anchors: VectorAnchor[] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tconst t = -Math.PI / 2 + (i * 2 * Math.PI) / n;\n\t\tanchors.push({ x: cx + (Math.cos(t) * b.width) / 2, y: cy + (Math.sin(t) * b.height) / 2 });\n\t}\n\treturn { anchors, closed: true };\n}\n\n/** A star inscribed in a box, first point at the top. `innerRatio` is inner / outer radius. */\nexport function starSubpath(b: VectorBounds, points = 5, innerRatio = 0.5): VectorSubpath {\n\tconst n = Math.max(3, Math.round(points));\n\tconst cx = b.x + b.width / 2;\n\tconst cy = b.y + b.height / 2;\n\tconst anchors: VectorAnchor[] = [];\n\tfor (let i = 0; i < n * 2; i++) {\n\t\tconst t = -Math.PI / 2 + (i * Math.PI) / n;\n\t\tconst r = i % 2 === 0 ? 1 : innerRatio;\n\t\tanchors.push({ x: cx + (Math.cos(t) * r * b.width) / 2, y: cy + (Math.sin(t) * r * b.height) / 2 });\n\t}\n\treturn { anchors, closed: true };\n}\n\n/** A straight open line. */\nexport function lineSubpath(a: Vec2, b: Vec2): VectorSubpath {\n\treturn { anchors: [{ x: a.x, y: a.y }, { x: b.x, y: b.y }], closed: false };\n}\n\n/** Rounds every coordinate to `precision` decimals (stable JSON and SVG output). */\nexport function roundSubpaths(subpaths: readonly VectorSubpath[], precision = 3): VectorSubpath[] {\n\tconst f = 10 ** precision;\n\tconst r = (n: number) => Math.round(n * f) / f;\n\tconst rp = (p: Vec2) => ({ x: r(p.x), y: r(p.y) });\n\treturn subpaths.map((sp) => ({\n\t\tclosed: sp.closed,\n\t\tanchors: sp.anchors.map((a) => ({ ...cloneAnchor(a), x: r(a.x), y: r(a.y), ...(a.in ? { in: rp(a.in) } : {}), ...(a.out ? { out: rp(a.out) } : {}) }))\n\t}));\n}\n","/**\n * Path operators of live path effects (After Effects' \"Add ▸\" path operations). Each maps a list\n * of paths to a list of the same length (one per shape above the operator), except `mergePaths`,\n * which combines them into one, and `repeatPaths`, which makes copies. Paths are in the space of\n * the node the effect belongs to.\n *\n * Ported from ArtCraft (MIT OR Apache-2.0, © ArtCraft Team and contributors; see\n * THIRD_PARTY_NOTICES.md): effectcraft `crates/path/src/ops.rs` — `trim`, `trim_individually`,\n * `trim_intervals`, `Measured::append`, `pucker_bloat`, `round_corners`, `offset`, `twist`,\n * `resample`, `emit`, `zigzag`, `WiggleParams`, `splitmix`, `hash`, `noise`, `wiggle`, `MergeMode`\n * and `merge`; and the `\"repeater\"` branch of `collect()` in `crates/render/src/shapes.rs` (copy\n * transforms, opacity ramp, fractional last copy, offset and composite order). Arc lengths come\n * from our own `segmentLength` / `segmentParamAtLength` / `trimSegments`, offsets from\n * `offsetPath`, booleans from `pathBoolean`, and Round Corners uses the live-corner maths in\n * `corners.ts` (a true radius, exact at any angle, where a curve meets a corner too).\n */\nimport type { Point } from '../canvas/types.js';\nimport {\n\tbezierPathBounds,\n\tcloseContour,\n\tcontoursToPath,\n\tpathToContours,\n\tsegmentDerivative,\n\tsegmentLength,\n\tsegmentParamAtLength,\n\tsegmentPoint,\n\tsubSegment,\n\ttransformBezierPath,\n\ttrimSegments,\n\ttype AffineMatrix,\n\ttype BezierPath,\n\ttype BezierSegment\n} from '../canvas/pathops/bezier.js';\nimport { pathBoolean } from '../canvas/pathops/boolean.js';\nimport { offsetPath } from '../canvas/pathops/stroke.js';\nimport { setSubpathCorners } from './corners.js';\nimport { bezierToSubpaths, multiplyMatrices, rotateMatrix, scaleMatrix, subpathsToBezier, translateMatrix } from './geometry.js';\nimport type { Vec2, VectorEffectParamsMap, VectorLineJoin, VectorMergeMode } from './types.js';\n\nconst EPS = 1e-9;\n\n// ---------------------------------------------------------------------------\n// Subpath helpers\n// ---------------------------------------------------------------------------\n\n/** One subpath as cubic or line segments (quadratics raised), the closing line included. */\ninterface Sub {\n\tsegs: Point[][];\n\tclosed: boolean;\n}\n\nconst degenerate = (s: BezierSegment) => s.every((p) => Math.abs(p[0] - s[0][0]) < EPS && Math.abs(p[1] - s[0][1]) < EPS);\n\nfunction raise(seg: BezierSegment): Point[] {\n\tif (seg.length !== 3) return seg.map((p) => [p[0], p[1]] as Point);\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\n/** The path's subpaths with at least one segment (a closed one's closing line included). */\nfunction subsOf(path: BezierPath): Sub[] {\n\treturn pathToContours(path)\n\t\t.map((c) => ({ segs: closeContour(c).filter((s) => !degenerate(s)).map(raise), closed: c.closed }))\n\t\t.filter((s) => s.segs.length > 0);\n}\n\nconst subsToPath = (subs: readonly Sub[]): BezierPath => contoursToPath(subs.map((s) => ({ segments: s.segs, closed: s.closed })));\n\nconst unit = (x: number, y: number): Point | null => {\n\tconst l = Math.hypot(x, y);\n\treturn l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;\n};\n\n/** Direction a segment leaves its start in (robust at a handle on its anchor). */\nfunction startTangent(s: BezierSegment): Point {\n\tfor (let i = 1; i < s.length; i++) {\n\t\tconst u = unit(s[i][0] - s[0][0], s[i][1] - s[0][1]);\n\t\tif (u) return u;\n\t}\n\treturn [1, 0];\n}\n\n/** Direction a segment arrives at its end in. */\nfunction endTangent(s: BezierSegment): Point {\n\tconst end = s[s.length - 1];\n\tfor (let i = s.length - 2; i >= 0; i--) {\n\t\tconst u = unit(end[0] - s[i][0], end[1] - s[i][1]);\n\t\tif (u) return u;\n\t}\n\treturn [1, 0];\n}\n\nfunction tangentAt(s: BezierSegment, t: number): Point {\n\tif (t <= 1e-9) return startTangent(s);\n\tif (t >= 1 - 1e-9) return endTangent(s);\n\tconst d = segmentDerivative(s, t);\n\treturn unit(d[0], d[1]) ?? startTangent(s);\n}\n\nconst perp = (v: Point): Point => [-v[1], v[0]];\n\n/** Total arc length of a path (closing lines included). */\nexport function effectPathLength(path: BezierPath): number {\n\tlet total = 0;\n\tfor (const s of subsOf(path)) for (const seg of s.segs) total += segmentLength(seg);\n\treturn total;\n}\n\n/** Number of on-curve points of a path (a closed subpath's start counted once). */\nexport function effectPathVertexCount(path: BezierPath): number {\n\treturn subsOf(path).reduce((n, s) => n + s.segs.length + (s.closed ? 0 : 1), 0);\n}\n\n// ---------------------------------------------------------------------------\n// Trim Paths\n// ---------------------------------------------------------------------------\n\n/** A path measured for trimming: its segments end to end and their lengths. */\nclass Measured {\n\treadonly segs: Point[][];\n\treadonly lens: number[];\n\treadonly total: number;\n\tconstructor(path: BezierPath) {\n\t\tthis.segs = subsOf(path).flatMap((s) => s.segs);\n\t\tthis.lens = this.segs.map((s) => segmentLength(s));\n\t\tthis.total = this.lens.reduce((a, b) => a + b, 0);\n\t}\n\n\t/**\n\t * Appends the part between lengths `sa` and `sb`, continuing the last piece when this one\n\t * starts where it ends (across the start of a closed path).\n\t */\n\tappend(sa: number, sb: number, out: Sub[]) {\n\t\tfor (const piece of trimSegments(this.segs, sa, sb, this.lens)) {\n\t\t\tif (degenerate(piece)) continue;\n\t\t\tconst last = out[out.length - 1]?.segs;\n\t\t\tconst end = last?.[last.length - 1]?.at(-1);\n\t\t\tif (!end || Math.hypot(end[0] - piece[0][0], end[1] - piece[0][1]) > 1e-6) out.push({ segs: [piece], closed: false });\n\t\t\telse last!.push(piece);\n\t\t}\n\t}\n}\n\n/** The fractions of the length a trim keeps, or null when it keeps everything. */\nexport function trimIntervals(start: number, end: number, offsetDegrees: number): [number, number][] | null {\n\tlet s = start / 100;\n\tlet e = end / 100;\n\tif (!Number.isFinite(s) || !Number.isFinite(e)) return null;\n\tif (s > e) [s, e] = [e, s];\n\ts = Math.min(1, Math.max(0, s));\n\te = Math.min(1, Math.max(0, e));\n\tif (e - s >= 1 - 1e-9) return null;\n\tif (e - s <= 1e-9) return [];\n\tconst off = Number.isFinite(offsetDegrees) ? (((offsetDegrees / 360) % 1) + 1) % 1 : 0;\n\ts += off;\n\te += off;\n\tif (e <= 1) return [[s, e]];\n\tif (s >= 1) return [[s - 1, e - 1]];\n\treturn [\n\t\t[s, 1],\n\t\t[0, e - 1]\n\t];\n}\n\n/**\n * Trim Paths, simultaneously: every path is trimmed by the same fractions of its own length.\n * `start` / `end` in percent, `offset` in degrees. Paths trimmed away entirely come back empty.\n */\nexport function trimPaths(paths: readonly BezierPath[], start: number, end: number, offsetDegrees = 0): BezierPath[] {\n\tconst iv = trimIntervals(start, end, offsetDegrees);\n\tif (!iv) return paths.slice();\n\treturn paths.map((p) => {\n\t\tconst m = new Measured(p);\n\t\tconst out: Sub[] = [];\n\t\tif (m.total > 0) for (const [a, b] of iv) m.append(a * m.total, b * m.total, out);\n\t\treturn subsToPath(out);\n\t});\n}\n\n/**\n * Trim Paths, individually: the paths are laid end to end in stacking order and trimmed as one\n * length, so they draw on one after another.\n */\nexport function trimPathsIndividually(paths: readonly BezierPath[], start: number, end: number, offsetDegrees = 0): BezierPath[] {\n\tconst iv = trimIntervals(start, end, offsetDegrees);\n\tif (!iv) return paths.slice();\n\tconst ms = paths.map((p) => new Measured(p));\n\tconst total = ms.reduce((n, m) => n + m.total, 0);\n\tlet base = 0;\n\treturn ms.map((m) => {\n\t\tconst out: Sub[] = [];\n\t\tfor (const [a, b] of iv) {\n\t\t\tconst la = Math.max(0, a * total - base);\n\t\t\tconst lb = Math.min(m.total, b * total - base);\n\t\t\tif (lb > la) m.append(la, lb, out);\n\t\t}\n\t\tbase += m.total;\n\t\treturn subsToPath(out);\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Pucker & Bloat\n// ---------------------------------------------------------------------------\n\n/**\n * Pucker & Bloat (`amount` in percent, −100 to 100). Positive (bloat) moves the points towards\n * the mean of a path's points and pushes the handles away from it, so the segments bulge\n * outwards; negative (pucker) does the opposite, giving spiky shapes.\n */\nexport function puckerBloatPaths(paths: readonly BezierPath[], amount: number): BezierPath[] {\n\tconst a = amount / 100;\n\tif (!a || !Number.isFinite(a)) return paths.slice();\n\treturn paths.map((p) => {\n\t\tconst verts = p.filter((c) => c.type !== 'Z') as Exclude<BezierPath[number], { type: 'Z' }>[];\n\t\tif (!verts.length) return p;\n\t\tconst cx = verts.reduce((s, c) => s + c.x, 0) / verts.length;\n\t\tconst cy = verts.reduce((s, c) => s + c.y, 0) / verts.length;\n\t\tconst mv = (x: number, y: number) => ({ x: x + (cx - x) * a, y: y + (cy - y) * a });\n\t\tconst mvh = (x: number, y: number) => ({ x: x + (x - cx) * a, y: y + (y - cy) * a });\n\t\tconst out: BezierPath = [];\n\t\tlet prev = { x: 0, y: 0 };\n\t\tlet start = { x: 0, y: 0 };\n\t\tconst curve = (c1: Vec2, c2: Vec2, q: Vec2) => {\n\t\t\tconst h1 = mvh(c1.x, c1.y);\n\t\t\tconst h2 = mvh(c2.x, c2.y);\n\t\t\tconst e = mv(q.x, q.y);\n\t\t\tout.push({ type: 'C', x1: h1.x, y1: h1.y, x2: h2.x, y2: h2.y, x: e.x, y: e.y });\n\t\t};\n\t\tfor (const c of p) {\n\t\t\tswitch (c.type) {\n\t\t\t\tcase 'M': {\n\t\t\t\t\tconst e = mv(c.x, c.y);\n\t\t\t\t\tout.push({ type: 'M', x: e.x, y: e.y });\n\t\t\t\t\tprev = start = { x: c.x, y: c.y };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'L':\n\t\t\t\t\tcurve(prev, c, c);\n\t\t\t\t\tprev = { x: c.x, y: c.y };\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'Q': {\n\t\t\t\t\tconst c1 = { x: prev.x + (2 / 3) * (c.x1 - prev.x), y: prev.y + (2 / 3) * (c.y1 - prev.y) };\n\t\t\t\t\tconst c2 = { x: c.x + (2 / 3) * (c.x1 - c.x), y: c.y + (2 / 3) * (c.y1 - c.y) };\n\t\t\t\t\tcurve(c1, c2, c);\n\t\t\t\t\tprev = { x: c.x, y: c.y };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'C':\n\t\t\t\t\tcurve({ x: c.x1, y: c.y1 }, { x: c.x2, y: c.y2 }, c);\n\t\t\t\t\tprev = { x: c.x, y: c.y };\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'Z':\n\t\t\t\t\t// The implicit closing line becomes a curve too.\n\t\t\t\t\tif (Math.hypot(prev.x - start.x, prev.y - start.y) > EPS) curve(prev, start, start);\n\t\t\t\t\tout.push({ type: 'Z' });\n\t\t\t\t\tprev = start;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn out;\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Round Corners\n// ---------------------------------------------------------------------------\n\n/**\n * Round Corners: every corner (where the direction jumps) gets a circular arc of `radius`,\n * shrunk to fit where the sides are short. Smooth points and the ends of open paths are kept.\n */\nexport function roundCornerPaths(paths: readonly BezierPath[], radius: number): BezierPath[] {\n\tif (!(radius > 0) || !Number.isFinite(radius)) return paths.slice();\n\treturn paths.map((p) => subpathsToBezier(bezierToSubpaths(p).map((sp) => setSubpathCorners(sp, radius))));\n}\n\n// ---------------------------------------------------------------------------\n// Offset Paths\n// ---------------------------------------------------------------------------\n\n/**\n * Offset Paths: grows (positive `amount`) or shrinks (negative) every path with the given join.\n * `copies` copies are made, copy i offset by `amount · (i + 1 + copyOffset)`, all in one path.\n */\nexport function offsetPaths(paths: readonly BezierPath[], amount: number, join: VectorLineJoin = 'miter', miterLimit = 4, copies = 1, copyOffset = 0): BezierPath[] {\n\tconst n = Math.min(1000, Math.max(1, Math.round(Number.isFinite(copies) ? copies : 1)));\n\treturn paths.map((p) => {\n\t\tconst out: BezierPath = [];\n\t\tfor (let i = 0; i < n; i++) out.push(...offsetPath(p, amount * (i + 1 + copyOffset), { join, miterLimit }));\n\t\treturn out;\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Twist\n// ---------------------------------------------------------------------------\n\n/**\n * Twist: turns the paths about `center` by an angle growing with the distance from it, from 0\n * at the centre to `angle` degrees at the farthest point. Segments are subdivided and their\n * handles mapped through the warp's derivative, which keeps the result smooth.\n */\nexport function twistPaths(paths: readonly BezierPath[], angle: number, center: Vec2): BezierPath[] {\n\tlet rmax = 0;\n\tfor (const p of paths) for (const c of p) if (c.type !== 'Z') rmax = Math.max(rmax, Math.hypot(c.x - center.x, c.y - center.y));\n\tconst a = (angle * Math.PI) / 180;\n\tif (!a || rmax <= 1e-9 || !Number.isFinite(a)) return paths.slice();\n\tconst k = a / rmax;\n\tconst rot = (v: Point, th: number): Point => {\n\t\tconst s = Math.sin(th);\n\t\tconst co = Math.cos(th);\n\t\treturn [v[0] * co - v[1] * s, v[0] * s + v[1] * co];\n\t};\n\tconst map = (p: Point): Point => {\n\t\tconst v: Point = [p[0] - center.x, p[1] - center.y];\n\t\tconst r = rot(v, k * Math.hypot(v[0], v[1]));\n\t\treturn [center.x + r[0], center.y + r[1]];\n\t};\n\t// The warp's derivative at `p` applied to `dv`.\n\tconst jac = (p: Point, dv: Point): Point => {\n\t\tconst v: Point = [p[0] - center.x, p[1] - center.y];\n\t\tconst r = Math.hypot(v[0], v[1]);\n\t\tlet w = dv;\n\t\tif (r > 1e-12) {\n\t\t\tconst f = (k * (v[0] * dv[0] + v[1] * dv[1])) / r;\n\t\t\tw = [dv[0] - v[1] * f, dv[1] + v[0] * f];\n\t\t}\n\t\treturn rot(w, k * r);\n\t};\n\treturn paths.map((p) => {\n\t\tconst out: Sub[] = [];\n\t\tfor (const sub of subsOf(p)) {\n\t\t\tconst segs: Point[][] = [];\n\t\t\tfor (const seg of sub.segs) {\n\t\t\t\tconst cubic = seg.length === 2 ? [seg[0], seg[0], seg[1], seg[1]] : seg;\n\t\t\t\tconst len = segmentLength(seg);\n\t\t\t\tconst m = Math.min(64, Math.max(1, Math.ceil((Math.abs(a) * len) / rmax / 0.15 + len / 50)));\n\t\t\t\tfor (let i = 0; i < m; i++) {\n\t\t\t\t\tconst q = subSegment(cubic, i / m, (i + 1) / m);\n\t\t\t\t\tconst p0 = map(q[0]);\n\t\t\t\t\tconst p3 = map(q[3]);\n\t\t\t\t\tconst h1 = jac(q[0], [q[1][0] - q[0][0], q[1][1] - q[0][1]]);\n\t\t\t\t\tconst h2 = jac(q[3], [q[2][0] - q[3][0], q[2][1] - q[3][1]]);\n\t\t\t\t\tsegs.push([p0, [p0[0] + h1[0], p0[1] + h1[1]], [p3[0] + h2[0], p3[1] + h2[1]], p3]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tout.push({ segs, closed: sub.closed });\n\t\t}\n\t\treturn subsToPath(out);\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Resampling (Zig Zag, Wiggle Paths)\n// ---------------------------------------------------------------------------\n\ninterface Sample {\n\tp: Point;\n\ttan: Point;\n\t/** Length of the piece before / after this sample. */\n\tlenPrev: number;\n\tlenNext: number;\n}\n\n/**\n * Splits every segment into `m` pieces of equal arc length. Original points get the average\n * of their incoming and outgoing directions (so a corner's normal is its bisector).\n */\nfunction resample(sub: Sub, m: number): Sample[] {\n\tconst segs = sub.segs;\n\tconst n = segs.length;\n\tconst lens = segs.map((s) => segmentLength(s));\n\tconst out: Sample[] = [];\n\tfor (let j = 0; j < n; j++) {\n\t\tconst piece = lens[j] / m;\n\t\tfor (let k = 0; k < m; k++) {\n\t\t\tif (k === 0) {\n\t\t\t\tconst tout = startTangent(segs[j]);\n\t\t\t\tconst prev = sub.closed || j > 0 ? (j + n - 1) % n : null;\n\t\t\t\tlet tan = tout;\n\t\t\t\tif (prev !== null) {\n\t\t\t\t\tconst tin = endTangent(segs[prev]);\n\t\t\t\t\ttan = unit(tin[0] + tout[0], tin[1] + tout[1]) ?? tout;\n\t\t\t\t}\n\t\t\t\tout.push({ p: segs[j][0], tan, lenPrev: prev === null ? 0 : lens[prev] / m, lenNext: piece });\n\t\t\t} else {\n\t\t\t\tconst t = segmentParamAtLength(segs[j], piece * k, lens[j]);\n\t\t\t\tout.push({ p: segmentPoint(segs[j], t), tan: tangentAt(segs[j], t), lenPrev: piece, lenNext: piece });\n\t\t\t}\n\t\t}\n\t}\n\tif (!sub.closed) {\n\t\tconst last = segs[n - 1];\n\t\tout.push({ p: last[last.length - 1], tan: endTangent(last), lenPrev: lens[n - 1] / m, lenNext: 0 });\n\t}\n\treturn out;\n}\n\n/** Displaced samples as corner (polyline) or smooth (tangent-aligned cubic) points; `handle` is a fraction of the pieces. */\nfunction emit(samples: readonly Sample[], disp: readonly Point[], closed: boolean, smooth: boolean, handle: number): Sub | null {\n\tconst n = samples.length;\n\tif (!n) return null;\n\tconst pt = (i: number): Point => [samples[i].p[0] + disp[i][0], samples[i].p[1] + disp[i][1]];\n\tconst segs: Point[][] = [];\n\tconst count = closed ? n : n - 1;\n\tfor (let i = 0; i < count; i++) {\n\t\tconst j = (i + 1) % n;\n\t\tconst a = samples[i];\n\t\tconst b = samples[j];\n\t\tconst p = pt(i);\n\t\tconst q = pt(j);\n\t\tif (smooth) segs.push([p, [p[0] + a.tan[0] * a.lenNext * handle, p[1] + a.tan[1] * a.lenNext * handle], [q[0] - b.tan[0] * b.lenPrev * handle, q[1] - b.tan[1] * b.lenPrev * handle], q]);\n\t\telse segs.push([p, q]);\n\t}\n\tif (!segs.length) return null;\n\treturn { segs, closed };\n}\n\n/**\n * Zig Zag: every segment gets `ridges` ridges: it is split into `ridges + 1` equal pieces and the\n * points (original ones included) are pushed alternately `size` to either side of the path.\n * `smooth` makes rounded waves. A closed path of n segments ends up with `n · (ridges + 1)` points\n * (an open one, one more).\n */\nexport function zigzagPaths(paths: readonly BezierPath[], size: number, ridges: number, smooth = false): BezierPath[] {\n\tconst m = Math.min(1000, Math.max(0, Math.round(Number.isFinite(ridges) ? ridges : 0))) + 1;\n\treturn paths.map((p) => {\n\t\tconst out: Sub[] = [];\n\t\tfor (const sub of subsOf(p)) {\n\t\t\tconst s = resample(sub, m);\n\t\t\tconst disp = s.map((x, i) => {\n\t\t\t\tconst n = perp(x.tan);\n\t\t\t\tconst d = i % 2 === 0 ? size : -size;\n\t\t\t\treturn [n[0] * d, n[1] * d] as Point;\n\t\t\t});\n\t\t\tconst e = emit(s, disp, sub.closed, smooth, 0.36);\n\t\t\tif (e) out.push(e);\n\t\t}\n\t\treturn subsToPath(out);\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Wiggle Paths\n// ---------------------------------------------------------------------------\n\nconst MASK64 = (1n << 64n) - 1n;\n\nfunction splitmix(x: bigint): bigint {\n\tx = (x + 0x9e3779b97f4a7c15n) & MASK64;\n\tlet z = x;\n\tz = ((z ^ (z >> 30n)) * 0xbf58476d1ce4e5b9n) & MASK64;\n\tz = ((z ^ (z >> 27n)) * 0x94d049bb133111ebn) & MASK64;\n\treturn z ^ (z >> 31n);\n}\n\n/** Deterministic hash → [−1, 1]. */\nfunction hash(seed: bigint, idx: bigint, t: number): number {\n\tconst h = splitmix(splitmix(splitmix(seed) ^ idx) ^ BigInt.asUintN(64, BigInt(t)));\n\treturn (Number(h >> 11n) / 2 ** 53) * 2 - 1;\n}\n\n/** Smooth 1D value noise in [−1, 1]: Catmull-Rom through hashed lattice values (C1 in time). */\nfunction noise(seed: bigint, idx: bigint, t: number): number {\n\tconst i = Math.floor(t);\n\tconst f = t - i;\n\tconst p0 = hash(seed, idx, i - 1);\n\tconst p1 = hash(seed, idx, i);\n\tconst p2 = hash(seed, idx, i + 1);\n\tconst p3 = hash(seed, idx, i + 2);\n\tconst v = 0.5 * (2 * p1 + (-p0 + p2) * f + (2 * p0 - 5 * p1 + 4 * p2 - p3) * f * f + (-p0 + 3 * p1 - 3 * p2 + p3) * f * f * f);\n\treturn Math.min(1, Math.max(-1, v));\n}\n\n/**\n * Wiggle Paths: subdivides the paths (`detail` points per segment) and moves every point by\n * smooth noise of up to `size` that evolves over `time` seconds at `speed` wiggles per second.\n * The same parameters always give the same result.\n */\nexport function wigglePaths(paths: readonly BezierPath[], w: VectorEffectParamsMap['wiggle']): BezierPath[] {\n\tif (!w.size || !Number.isFinite(w.size)) return paths.slice();\n\tconst m = Math.min(1000, Math.max(0, Math.round(Number.isFinite(w.detail) ? w.detail : 0))) + 1;\n\tconst seed = BigInt.asUintN(64, BigInt(Math.round(Number.isFinite(w.seed) ? w.seed : 0)));\n\tconst t = (Number.isFinite(w.time) ? w.time : 0) * (Number.isFinite(w.speed) ? w.speed : 0) + (Number.isFinite(w.phase) ? w.phase : 0) / 360;\n\tconst corr = Math.min(1, Math.max(0, (Number.isFinite(w.correlation) ? w.correlation : 0) / 100));\n\tconst sx = noise(seed, MASK64, t);\n\tconst sy = noise(seed, MASK64 - 1n, t);\n\tlet idx = 0;\n\treturn paths.map((p) => {\n\t\tconst out: Sub[] = [];\n\t\tfor (const sub of subsOf(p)) {\n\t\t\tconst s = resample(sub, m);\n\t\t\tconst disp = s.map(() => {\n\t\t\t\tconst i = BigInt(idx);\n\t\t\t\tconst ox = noise(seed, i * 2n, t + idx * 0.37);\n\t\t\t\tconst oy = noise(seed, i * 2n + 1n, t + idx * 0.61);\n\t\t\t\tidx++;\n\t\t\t\treturn [(sx * corr + ox * (1 - corr)) * w.size, (sy * corr + oy * (1 - corr)) * w.size] as Point;\n\t\t\t});\n\t\t\tconst e = emit(s, disp, sub.closed, w.smooth, 1 / 3);\n\t\t\tif (e) out.push(e);\n\t\t}\n\t\treturn subsToPath(out);\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Merge Paths\n// ---------------------------------------------------------------------------\n\nconst MERGE_OPS = { add: 'union', subtract: 'difference', intersect: 'intersection', exclude: 'xor' } as const;\n\n/** Merge Paths: the paths (in stacking order, each filled non-zero) combined into one path. */\nexport function mergePaths(paths: readonly BezierPath[], mode: VectorMergeMode = 'merge'): BezierPath {\n\tif (mode === 'merge' || !(mode in MERGE_OPS)) return paths.flat();\n\tconst inputs = paths.filter((p) => p.length > 0).map((path) => ({ path, fillRule: 'nonzero' as const }));\n\tif (!inputs.length) return [];\n\treturn pathBoolean(MERGE_OPS[mode as keyof typeof MERGE_OPS], inputs).path;\n}\n\n// ---------------------------------------------------------------------------\n// Repeater\n// ---------------------------------------------------------------------------\n\n/** One copy a repeater makes: its transform (node space) and opacity factor. */\nexport interface VectorRepeaterCopy {\n\t/** Copy number (0 is the original). */\n\tindex: number;\n\tmatrix: AffineMatrix;\n\topacity: number;\n}\n\n/**\n * The copies of a repeater around `pivot` (the anchor point in node space), in copy order.\n * Copy i is moved by `translate(position·k) · rotate(rotation·k) · scale^k` about the pivot with\n * `k = i + offset`; opacity runs from the start to the end opacity across the copies, and a\n * fractional copy count fades the last copy by its fraction.\n */\nexport function vectorRepeaterCopies(r: VectorEffectParamsMap['repeater'], pivot: Vec2): VectorRepeaterCopy[] {\n\tconst copies = Math.max(0, Number.isFinite(r.copies) ? Math.min(r.copies, 1000) : 0);\n\tconst n = Math.ceil(copies);\n\tconst offset = Number.isFinite(r.offset) ? r.offset : 0;\n\tconst so = (r.startOpacity ?? 100) / 100;\n\tconst eo = (r.endOpacity ?? 100) / 100;\n\tconst fract = copies - Math.floor(copies);\n\tconst out: VectorRepeaterCopy[] = [];\n\tfor (let i = 0; i < n; i++) {\n\t\tconst k = i + offset;\n\t\tlet m = translateMatrix(-pivot.x, -pivot.y);\n\t\tm = multiplyMatrices(m, scaleMatrix(Math.pow((r.scale?.x ?? 100) / 100, k), Math.pow((r.scale?.y ?? 100) / 100, k)));\n\t\tm = multiplyMatrices(m, rotateMatrix((r.rotation ?? 0) * k));\n\t\tm = multiplyMatrices(m, translateMatrix(pivot.x + (r.position?.x ?? 0) * k, pivot.y + (r.position?.y ?? 0) * k));\n\t\tconst t = n > 1 ? i / (n - 1) : 0;\n\t\tconst partial = i + 1 === n && fract > 0 ? fract : 1;\n\t\tout.push({ index: i, matrix: m, opacity: Math.min(1, Math.max(0, (so + (eo - so) * t) * partial)) });\n\t}\n\treturn out;\n}\n\n/** Each path repeated by `vectorRepeaterCopies` (copy order, the original first). */\nexport function repeatPaths(paths: readonly BezierPath[], r: VectorEffectParamsMap['repeater'], pivot: Vec2): { copy: VectorRepeaterCopy; paths: BezierPath[] }[] {\n\treturn vectorRepeaterCopies(r, pivot).map((copy) => ({ copy, paths: paths.map((p) => transformBezierPath(p, copy.matrix)) }));\n}\n\n/** The middle of the paths' bounds (the origin when they are empty). */\nexport function pathsCenter(paths: readonly BezierPath[]): Vec2 {\n\tconst b = bezierPathBounds(paths.flat());\n\treturn b ? { x: (b.minX + b.maxX) / 2, y: (b.minY + b.maxY) / 2 } : { x: 0, y: 0 };\n}\n","// Portions ported from effectcraft-main/crates/path/src/interp.rs, vectorcraft-main/crates/doc/src/blend.rs and deckcraft-main/crates/geom/src/morph.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n/**\n * Morphing between vector paths: bring two shapes to matching anchors, then compute in-betweens.\n *\n * - **Contours** pair up in order. A contour without a partner grows out of (or shrinks into) a\n * point at the same place relative to the other shape's box. Straight segments become cubics\n * with handles at a third of the way, so a line bends smoothly into a curve.\n * - **Anchors** are inserted by arc length with de Casteljau splits, so both contours of a pair\n * keep their exact outline and get the same number of segments, spread over the segments in\n * proportion to their length.\n * - **Correspondence**: the closed side of each pair is turned to the cyclic start that costs\n * least — the position differences left after the best-fit rotation (both shapes centred and\n * scaled to unit size), a little for the turn, plus the χ² distance of log-polar\n * shape-context histograms (Belongie, Malik & Puzicha, PAMI 2002; angles taken from the\n * contour's direction) — and the winding is chosen once for all contours (the first closed\n * pair turns the same way), so a nonzero fill keeps its holes.\n * - **In-betweens** follow the shapes' best-fit rotation and scale (Procrustes), with the\n * leftover difference blended in linearly, so a turning shape turns instead of collapsing.\n * - **Paint**: colours mix in OKLab, gradients of one style resample their stops to a common\n * set, a missing paint fades from transparent; stroke width, opacity, dashes, arrowhead\n * scales and width profiles interpolate, and the rest switches halfway.\n * - **Transforms**: both shapes are blended in their parent's space; the in-between's rigid\n * `transform` interpolates as a rotation (the short way round) plus a translation.\n *\n * Live corners are cut into the geometry first, so rounded corners morph as curves.\n */\nimport type { Point } from '../canvas/types.js';\nimport { segmentLength, segmentParamAtLength, splitSegment } from '../canvas/pathops/bezier.js';\nimport { expandVectorGradientStops, explicitVectorGradient, hasExplicitGradientGeometry, isVectorGradient, sampleVectorGradient, sortedVectorGradientStops, vectorGradientPoints } from './gradient.js';\nimport { mixColorsOklab } from '../animation/color.js';\nimport { evaluateEasing, type AnimationEasing } from '../animation/easing.js';\nimport { expandVectorCorners } from './corners.js';\nimport {\n\tapplyMatrix,\n\thasPaint,\n\tIDENTITY_MATRIX,\n\tinferAnchorType,\n\tinvertMatrix,\n\tisIdentityMatrix,\n\tmultiplyMatrices,\n\tnodeMatrix,\n\tunionBounds,\n\ttype AffineMatrix\n} from './geometry.js';\nimport { reverseWidthProfile, widthProfileAt } from './width.js';\nimport type { Vec2, VectorAnchor, VectorBounds, VectorGradient, VectorGradientStop, VectorGroupNode, VectorNode, VectorPaint, VectorPathNode, VectorStyle, VectorSubpath, VectorWidthPoint } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface VectorMorphOptions {\n\t/**\n\t * Move anchors in straight lines instead of following the shapes' best-fit rotation and\n\t * scale. Default `false`.\n\t */\n\tlinear?: boolean;\n\t/** 0–1: how much the rotation-aware path outweighs the straight one (default 1). */\n\trigidity?: number;\n\t/** Fewest segments each contour pair gets (default 16). */\n\tminSegments?: number;\n\t/** At most this far apart (document units) along the longer contour, when set. */\n\tspacing?: number | null;\n\t/** Most segments a contour pair gets (default 400). */\n\tmaxSegments?: number;\n\t/** Search the start anchor of closed contours (default `true`); off keeps the stored starts. */\n\tmatchStart?: boolean;\n\t/** Compare shape-context histograms when choosing starts (default `true`). */\n\tshapeContext?: boolean;\n\t/**\n\t * The anchor each shape's first subpath starts from (the Blend tool's clicks). A closed\n\t * subpath turns round to it; an open one clicked at its last anchor runs backwards.\n\t */\n\tstarts?: readonly [number | null | undefined, number | null | undefined];\n\t/** Id of the in-between (default: the first shape's id). */\n\tid?: string;\n}\n\n/** Two shapes prepared once; `at(t)` interpolates (0 = `a`, 1 = `b`). */\nexport interface VectorMorph {\n\tat(t: number): VectorNode;\n}\n\n// ---------------------------------------------------------------------------\n// Point helpers\n// ---------------------------------------------------------------------------\n\nconst lerpN = (a: number, b: number, t: number) => a + (b - a) * t;\nconst lerpP = (a: Point, b: Point, t: number): Point => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];\nconst toP = (v: { x: number; y: number }): Point => [v.x, v.y];\nconst mapP = (m: AffineMatrix, p: Point): Point => [m[0] * p[0] + m[2] * p[1] + m[4], m[1] * p[0] + m[3] * p[1] + m[5]];\n\n// ---------------------------------------------------------------------------\n// Contours: a start anchor, then three points (handle, handle, anchor) per cubic segment\n// ---------------------------------------------------------------------------\n\n/** A subpath as cubics. A closed contour's last point repeats its first. */\nexport interface VectorMorphContour {\n\tclosed: boolean;\n\tpts: Point[];\n}\n\nconst curvesOf = (c: VectorMorphContour) => Math.max(0, (c.pts.length - 1) / 3) | 0;\n\n/** A subpath mapped by `m` as cubics (lines with handles at thirds), or null when empty. */\nexport function subpathToMorphContour(sp: VectorSubpath, m: AffineMatrix = IDENTITY_MATRIX): VectorMorphContour | null {\n\tconst n = sp.anchors.length;\n\tif (!n) return null;\n\tconst first = mapP(m, toP(sp.anchors[0]));\n\tconst pts: Point[] = [first];\n\tconst count = n < 2 ? 0 : sp.closed ? n : n - 1;\n\tfor (let i = 0; i < count; i++) {\n\t\tconst a = sp.anchors[i];\n\t\tconst b = sp.anchors[(i + 1) % n];\n\t\tconst pa = toP(a);\n\t\tconst pb = toP(b);\n\t\tconst straight = (!a.out || (a.out.x === a.x && a.out.y === a.y)) && (!b.in || (b.in.x === b.x && b.in.y === b.y));\n\t\tconst c1 = straight ? lerpP(pa, pb, 1 / 3) : toP(a.out ?? a);\n\t\tconst c2 = straight ? lerpP(pa, pb, 2 / 3) : toP(b.in ?? b);\n\t\tpts.push(mapP(m, c1), mapP(m, c2), i === count - 1 && sp.closed ? first : mapP(m, pb));\n\t}\n\treturn { closed: sp.closed && count > 0, pts };\n}\n\n/** Whether the cubic's handles lie on its chord (it draws a straight line). */\nfunction isStraight(p0: Point, p1: Point, p2: Point, p3: Point): boolean {\n\tconst dx = p3[0] - p0[0];\n\tconst dy = p3[1] - p0[1];\n\tconst len = Math.hypot(dx, dy);\n\tconst tol = Math.max(1e-7, len * 1e-7);\n\tif (len < 1e-12) return Math.hypot(p1[0] - p0[0], p1[1] - p0[1]) < tol && Math.hypot(p2[0] - p0[0], p2[1] - p0[1]) < tol;\n\tconst off = (p: Point) => Math.abs((p[0] - p0[0]) * dy - (p[1] - p0[1]) * dx) / len;\n\tconst along = (p: Point) => ((p[0] - p0[0]) * dx + (p[1] - p0[1]) * dy) / (len * len);\n\tconst u1 = along(p1);\n\tconst u2 = along(p2);\n\treturn off(p1) < tol && off(p2) < tol && u1 >= -1e-9 && u1 <= 1 + 1e-9 && u2 >= -1e-9 && u2 <= 1 + 1e-9;\n}\n\n/** A contour back to a subpath (`m` maps it out of the morph's space). Straight cubics become lines. */\nexport function morphContourToSubpath(c: VectorMorphContour, m: AffineMatrix = IDENTITY_MATRIX, closed = c.closed): VectorSubpath {\n\tconst pts = isIdentityMatrix(m) ? c.pts : c.pts.map((p) => mapP(m, p));\n\tconst curves = curvesOf(c);\n\t// A closed contour drops its repeated last anchor; one opened mid-morph keeps it.\n\tconst dropLast = closed && c.closed && curves > 0;\n\tconst count = dropLast ? curves : curves + 1;\n\tconst anchors: VectorAnchor[] = [];\n\tfor (let k = 0; k < count; k++) anchors.push({ x: pts[3 * k][0], y: pts[3 * k][1] });\n\tfor (let k = 0; k < curves; k++) {\n\t\tconst p0 = pts[3 * k];\n\t\tconst p1 = pts[3 * k + 1];\n\t\tconst p2 = pts[3 * k + 2];\n\t\tconst p3 = pts[3 * k + 3];\n\t\tif (isStraight(p0, p1, p2, p3)) continue;\n\t\tanchors[k].out = { x: p1[0], y: p1[1] };\n\t\tanchors[(k + 1) % count].in = { x: p2[0], y: p2[1] };\n\t}\n\tfor (const a of anchors) {\n\t\tconst type = inferAnchorType(a);\n\t\tif (type !== 'corner') a.type = type;\n\t}\n\tconst isClosed = closed && curves > 0;\n\treturn { anchors: dropCollinear(anchors, isClosed), closed: isClosed };\n}\n\n/** Anchors without the ones that sit inside a straight run (no handles, in line with both neighbours). */\nfunction dropCollinear(anchors: VectorAnchor[], closed: boolean): VectorAnchor[] {\n\tconst out = anchors.slice();\n\tconst min = closed ? 3 : 2;\n\tlet i = closed ? 0 : 1;\n\twhile (out.length > min && i < out.length - (closed ? 0 : 1)) {\n\t\tconst n = out.length;\n\t\tconst a = out[(i - 1 + n) % n];\n\t\tconst b = out[i];\n\t\tconst c = out[(i + 1) % n];\n\t\tconst plain = !b.in && !b.out && !a.out && !c.in;\n\t\tconst ux = b.x - a.x;\n\t\tconst uy = b.y - a.y;\n\t\tconst vx = c.x - b.x;\n\t\tconst vy = c.y - b.y;\n\t\tconst len = Math.hypot(ux, uy) + Math.hypot(vx, vy);\n\t\tif (plain && Math.abs(ux * vy - uy * vx) <= 1e-9 * Math.max(1, len * len) && ux * vx + uy * vy >= 0) out.splice(i, 1);\n\t\telse i++;\n\t}\n\treturn out;\n}\n\nconst segOf = (c: VectorMorphContour, k: number): Point[] => c.pts.slice(3 * k, 3 * k + 4);\n\n/**\n * Exactly the same outline with `segments` cubics (at least as many as it has), the new\n * anchors spread over its segments in proportion to their arc length (largest remainder)\n * and each segment split into pieces of equal arc length. Also returns where each original\n * anchor went.\n */\nexport function refineMorphContour(c: VectorMorphContour, segments: number): { contour: VectorMorphContour; anchors: number[] } {\n\tconst have = curvesOf(c);\n\tif (have === 0) return { contour: { closed: c.closed, pts: Array.from({ length: 1 + 3 * segments }, () => [c.pts[0][0], c.pts[0][1]] as Point) }, anchors: [0] };\n\tconst identity = Array.from({ length: c.closed ? have : have + 1 }, (_, i) => i);\n\tif (have >= segments) return { contour: c, anchors: identity };\n\tconst segs = Array.from({ length: have }, (_, k) => segOf(c, k));\n\tconst lens = segs.map((s) => segmentLength(s));\n\tconst sum = lens.reduce((a, b) => a + b, 0);\n\tconst extra = segments - have;\n\tconst want = lens.map((l) => (sum > 1e-12 ? (l / sum) * extra : extra / have));\n\tconst parts = want.map((w) => 1 + Math.floor(w));\n\tlet left = segments - parts.reduce((a, b) => a + b, 0);\n\t// Remainders that differ by rounding noise tie, so congruent shapes split alike.\n\tconst frac = want.map((w) => w - Math.floor(w));\n\tconst order = parts.map((_, i) => i).sort((a, b) => (Math.abs(frac[b] - frac[a]) > 1e-9 ? frac[b] - frac[a] : a - b));\n\tfor (let i = 0; left > 0; i = (i + 1) % order.length, left--) parts[order[i]]++;\n\tconst pts: Point[] = [c.pts[0]];\n\tconst anchors: number[] = [0];\n\tlet at = 0;\n\tsegs.forEach((seg, k) => {\n\t\tconst pieces = parts[k];\n\t\tlet rest: Point[] = seg.slice();\n\t\tlet t0 = 0;\n\t\tfor (let j = 1; j < pieces; j++) {\n\t\t\tconst t = segmentParamAtLength(seg, (lens[k] * j) / pieces, lens[k]);\n\t\t\tconst u = Math.min(1, Math.max(0, (t - t0) / (1 - t0 || 1)));\n\t\t\tconst [l, r] = splitSegment(rest, u);\n\t\t\tpts.push(l[1], l[2], l[3]);\n\t\t\trest = r;\n\t\t\tt0 = t;\n\t\t}\n\t\tpts.push(rest[1], rest[2], k === have - 1 && c.closed ? c.pts[0] : rest[3]);\n\t\tat += pieces;\n\t\tif (k < have - 1 || !c.closed) anchors.push(at);\n\t});\n\treturn { contour: { closed: c.closed, pts }, anchors };\n}\n\n/** The same contour run backwards (a closed one keeps its start anchor). */\nexport function reverseMorphContour(c: VectorMorphContour): VectorMorphContour {\n\treturn { closed: c.closed, pts: c.pts.slice().reverse() };\n}\n\n/** A closed contour starting at anchor `s` (deckcraft's `turn`). */\nexport function turnMorphContour(c: VectorMorphContour, s: number): VectorMorphContour {\n\tconst n = curvesOf(c);\n\tif (!c.closed || n === 0) return c;\n\tconst k = ((s % n) + n) % n;\n\tif (k === 0) return c;\n\tconst body = c.pts.slice(1);\n\treturn { closed: true, pts: [c.pts[3 * k], ...body.slice(3 * k), ...body.slice(0, 3 * k)] };\n}\n\n/** Anchor positions of a contour (a closed one without its repeated end). */\nfunction anchorPoints(c: VectorMorphContour): Point[] {\n\tconst n = curvesOf(c);\n\tconst count = c.closed ? n : n + 1;\n\treturn Array.from({ length: Math.max(1, count) }, (_, k) => c.pts[3 * k]);\n}\n\n/** Signed area of a contour's anchor polygon. */\nfunction polygonArea(pts: readonly Point[]): number {\n\tlet s = 0;\n\tfor (let i = 0; i < pts.length; i++) {\n\t\tconst a = pts[i];\n\t\tconst b = pts[(i + 1) % pts.length];\n\t\ts += a[0] * b[1] - b[0] * a[1];\n\t}\n\treturn s / 2;\n}\n\n// ---------------------------------------------------------------------------\n// Correspondence\n// ---------------------------------------------------------------------------\n\n/** Centroid and RMS radius. */\nfunction frame(v: readonly Point[]): { c: Point; r: number } {\n\tconst n = Math.max(1, v.length);\n\tlet cx = 0;\n\tlet cy = 0;\n\tfor (const p of v) {\n\t\tcx += p[0];\n\t\tcy += p[1];\n\t}\n\tcx /= n;\n\tcy /= n;\n\tlet s = 0;\n\tfor (const p of v) s += (p[0] - cx) ** 2 + (p[1] - cy) ** 2;\n\treturn { c: [cx, cy], r: Math.max(1e-9, Math.sqrt(s / n)) };\n}\n\nconst RBINS = 5;\nconst ABINS = 12;\n/** Weight of the turn a start implies: among starts that fit equally well, the least turning wins. */\nconst TURN_WEIGHT = 0.1;\n\n/**\n * Log-polar shape-context histograms of every point, distances normalised by the RMS radius and\n * angles measured from the contour's direction at the point, so turning a shape leaves them\n * unchanged (only the fit below decides how far it turns).\n */\nfunction shapeContexts(v: readonly Point[], closed: boolean): Float64Array[] {\n\tconst { r } = frame(v);\n\tconst n = v.length;\n\treturn v.map((p, i) => {\n\t\tconst prev = v[closed ? (i - 1 + n) % n : Math.max(0, i - 1)];\n\t\tconst next = v[closed ? (i + 1) % n : Math.min(n - 1, i + 1)];\n\t\tconst dir = Math.atan2(next[1] - prev[1], next[0] - prev[0]);\n\t\tconst h = new Float64Array(RBINS * ABINS);\n\t\tlet total = 0;\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\tif (i === j) continue;\n\t\t\tconst dx = v[j][0] - p[0];\n\t\t\tconst dy = v[j][1] - p[1];\n\t\t\tconst d = Math.hypot(dx, dy) / r;\n\t\t\t// log radius in [1/8, 2] → five bins.\n\t\t\tconst lr = ((Math.log2(Math.max(d, 1e-6)) + 3) / 4) * RBINS;\n\t\t\tif (!(lr >= 0 && lr < RBINS)) continue;\n\t\t\tlet ang = Math.atan2(dy, dx) - dir;\n\t\t\tang = ((ang % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI);\n\t\t\th[Math.floor(lr) * ABINS + Math.min(ABINS - 1, Math.floor((ang / (2 * Math.PI)) * ABINS))] += 1;\n\t\t\ttotal++;\n\t\t}\n\t\tif (total > 0) for (let k = 0; k < h.length; k++) h[k] /= total;\n\t\treturn h;\n\t});\n}\n\nfunction chi2(a: Float64Array, b: Float64Array): number {\n\tlet s = 0;\n\tfor (let k = 0; k < a.length; k++) {\n\t\tconst t = a[k] + b[k];\n\t\tif (t > 1e-12) s += ((a[k] - b[k]) * (a[k] - b[k])) / t;\n\t}\n\treturn s / 2;\n}\n\n/**\n * The cyclic start of the closed `free` contour that matches `fixed` best, and its cost. Both\n * shapes are centred and scaled to unit RMS radius (where and how big they are does not\n * matter); a start costs what is left after the best rotation maps one onto the other, plus a\n * little for how far that rotation turns, plus (optionally) the mean χ² shape-context\n * distance. So a turned copy of a shape matches by turning rather than by squashing.\n */\nfunction bestStart(fixed: VectorMorphContour, free: VectorMorphContour, useShapeContext: boolean): { start: number; cost: number } {\n\tconst fa = anchorPoints(fixed);\n\tconst ga = anchorPoints(free);\n\tconst n = ga.length;\n\tconst ff = frame(fa);\n\tconst gf = frame(ga);\n\tconst fn = fa.map((p): Point => [(p[0] - ff.c[0]) / ff.r, (p[1] - ff.c[1]) / ff.r]);\n\tconst gn = ga.map((p): Point => [(p[0] - gf.c[0]) / gf.r, (p[1] - gf.c[1]) / gf.r]);\n\tconst fsc = useShapeContext ? shapeContexts(fa, fixed.closed) : null;\n\tconst gsc = useShapeContext ? shapeContexts(ga, free.closed) : null;\n\tconst m = Math.max(1, fn.length);\n\tconst cost = (s: number) => {\n\t\tlet dot = 0;\n\t\tlet cross = 0;\n\t\tlet sc = 0;\n\t\tfor (let i = 0; i < fn.length; i++) {\n\t\t\tconst j = free.closed ? (i + s) % n : i;\n\t\t\tconst p = fn[i];\n\t\t\tconst q = gn[j];\n\t\t\tdot += p[0] * q[0] + p[1] * q[1];\n\t\t\tcross += p[0] * q[1] - p[1] * q[0];\n\t\t\tif (fsc && gsc) sc += chi2(fsc[i], gsc[j]);\n\t\t}\n\t\t// Unit RMS on both sides: what the best rotation and scale leave over, as a fraction.\n\t\tconst residual = Math.max(0, 1 - (dot * dot + cross * cross) / (m * m));\n\t\tconst turn = 1 - Math.cos(Math.atan2(cross, dot));\n\t\treturn residual + TURN_WEIGHT * turn + sc / m;\n\t};\n\tif (!free.closed) return { start: 0, cost: cost(0) };\n\t// Long contours: every few starts first, then the neighbours of the best.\n\tconst stride = n > 96 ? Math.ceil(n / 48) : 1;\n\tlet best = { start: 0, cost: Infinity };\n\tfor (let s = 0; s < n; s += stride) {\n\t\tconst c = cost(s);\n\t\tif (c < best.cost - 1e-12) best = { start: s, cost: c };\n\t}\n\tif (stride > 1) {\n\t\tconst s0 = best.start;\n\t\tfor (let d = 1; d < stride; d++)\n\t\t\tfor (const s of [(s0 + d) % n, (s0 - d + n) % n]) {\n\t\t\t\tconst c = cost(s);\n\t\t\t\tif (c < best.cost - 1e-12) best = { start: s, cost: c };\n\t\t\t}\n\t}\n\treturn best;\n}\n\n/** The spot a contour without partner grows from: the same place relative to the other shape's box. */\nfunction placeIn(c: VectorMorphContour, from: VectorBounds | null, to: VectorBounds | null): Point {\n\tconst b = unionBounds([boundsOfPoints(c.pts)]);\n\tconst p: Point = b ? [b.x + b.width / 2, b.y + b.height / 2] : c.pts[0];\n\tif (!from || !to) return p;\n\tconst fx = from.width > 0 ? (p[0] - from.x) / from.width : 0.5;\n\tconst fy = from.height > 0 ? (p[1] - from.y) / from.height : 0.5;\n\treturn [to.x + to.width * fx, to.y + to.height * fy];\n}\n\nfunction boundsOfPoints(pts: readonly Point[]): VectorBounds | null {\n\tif (!pts.length) return null;\n\tlet x0 = Infinity;\n\tlet y0 = Infinity;\n\tlet x1 = -Infinity;\n\tlet y1 = -Infinity;\n\tfor (const p of pts) {\n\t\tx0 = Math.min(x0, p[0]);\n\t\ty0 = Math.min(y0, p[1]);\n\t\tx1 = Math.max(x1, p[0]);\n\t\ty1 = Math.max(y1, p[1]);\n\t}\n\treturn { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };\n}\n\n/** Two shapes' contours with matching structure: same count, and per pair the same segments. */\nexport interface VectorMorphCorrespondence {\n\ta: VectorMorphContour[];\n\tb: VectorMorphContour[];\n\t/** Whether `b` runs backwards relative to its stored direction. */\n\treversed: boolean;\n}\n\n/**\n * Gives two lists of contours the same structure with matching anchors (see the module notes).\n * Contours pair in order; one without a partner meets a point.\n */\nexport function correspondMorphContours(a: readonly VectorMorphContour[], b: readonly VectorMorphContour[], options: VectorMorphOptions = {}): VectorMorphCorrespondence {\n\tconst minSeg = Math.max(1, Math.round(options.minSegments ?? 16));\n\tconst maxSeg = Math.max(minSeg, Math.round(options.maxSegments ?? 400));\n\tconst spacing = options.spacing && options.spacing > 0 ? options.spacing : null;\n\tconst useSC = options.shapeContext !== false;\n\tconst match = options.matchStart !== false;\n\tconst ba = boundsOfPoints(a.flatMap((c) => c.pts));\n\tconst bb = boundsOfPoints(b.flatMap((c) => c.pts));\n\tconst contourLength = (c: VectorMorphContour) => {\n\t\tlet l = 0;\n\t\tfor (let k = 0; k < curvesOf(c); k++) l += segmentLength(segOf(c, k));\n\t\treturn l;\n\t};\n\ttype Pair = { x: VectorMorphContour; y: VectorMorphContour; point: boolean; startX: number | null; startY: number | null };\n\tconst pairs: Pair[] = [];\n\tconst [sa, sb] = options.starts ?? [];\n\tfor (let k = 0; k < Math.max(a.length, b.length); k++) {\n\t\tconst ca = a[k];\n\t\tconst cb = b[k];\n\t\tconst explicitA = k === 0 && sa !== null && sa !== undefined ? sa : null;\n\t\tconst explicitB = k === 0 && sb !== null && sb !== undefined ? sb : null;\n\t\tconst counts = [ca ? curvesOf(ca) : 0, cb ? curvesOf(cb) : 0];\n\t\tconst longest = Math.max(ca ? contourLength(ca) : 0, cb ? contourLength(cb) : 0);\n\t\tlet n = Math.max(counts[0], counts[1], minSeg, spacing ? Math.ceil(longest / spacing) : 0);\n\t\tn = Math.max(counts[0], counts[1], Math.min(n, maxSeg));\n\t\tconst point = (c: VectorMorphContour, from: VectorBounds | null, to: VectorBounds | null): VectorMorphContour => {\n\t\t\tconst p = placeIn(c, from, to);\n\t\t\treturn { closed: c.closed, pts: Array.from({ length: 1 + 3 * n }, () => [p[0], p[1]] as Point) };\n\t\t};\n\t\tif (ca && cb) {\n\t\t\tconst ra = refineMorphContour(ca, n);\n\t\t\tconst rb = refineMorphContour(cb, n);\n\t\t\tpairs.push({\n\t\t\t\tx: ra.contour,\n\t\t\t\ty: rb.contour,\n\t\t\t\tpoint: false,\n\t\t\t\tstartX: explicitA === null ? null : (ra.anchors[explicitA] ?? null),\n\t\t\t\tstartY: explicitB === null ? null : explicitB === rb.anchors.length - 1 && !cb.closed ? -1 : (rb.anchors[explicitB] ?? null)\n\t\t\t});\n\t\t\tif (explicitA !== null && !ca.closed && explicitA === ra.anchors.length - 1 && explicitA > 0) pairs[pairs.length - 1].startX = -1;\n\t\t} else if (ca) pairs.push({ x: refineMorphContour(ca, n).contour, y: point(ca, ba, bb), point: true, startX: null, startY: null });\n\t\telse if (cb) pairs.push({ x: point(cb, bb, ba), y: refineMorphContour(cb, n).contour, point: true, startX: null, startY: null });\n\t}\n\t// Explicit starts: closed contours turn to the clicked anchor; an open one clicked at its end runs backwards.\n\tconst startAt = (c: VectorMorphContour, s: number | null): VectorMorphContour => {\n\t\tif (s === null) return c;\n\t\tif (s === -1) return reverseMorphContour(c);\n\t\treturn c.closed ? turnMorphContour(c, s) : c;\n\t};\n\tconst explicit = pairs.length > 0 && (pairs[0].startX !== null || pairs[0].startY !== null);\n\t// Winding: closed shapes keep the same turning direction (effectcraft), judged on the first\n\t// pair of closed contours and applied to every contour so a nonzero fill keeps its holes;\n\t// open paths take whichever direction costs less (deckcraft), unless a click fixed it.\n\tconst ref = pairs.find((p) => !p.point && p.x.closed && p.y.closed);\n\tconst fixedBack = ref\n\t\t? Math.sign(polygonArea(anchorPoints(ref.x))) !== Math.sign(polygonArea(anchorPoints(ref.y))) && Math.abs(polygonArea(anchorPoints(ref.y))) > 1e-12\n\t\t: explicit\n\t\t\t? false\n\t\t\t: null;\n\tlet best: { xs: VectorMorphContour[]; ys: VectorMorphContour[]; cost: number; back: boolean } | null = null;\n\tfor (const back of fixedBack === null ? [false, true] : [fixedBack]) {\n\t\tconst xs: VectorMorphContour[] = [];\n\t\tconst ys: VectorMorphContour[] = [];\n\t\tlet cost = 0;\n\t\tpairs.forEach((p, k) => {\n\t\t\tlet x = k === 0 ? startAt(p.x, p.startX) : p.x;\n\t\t\tlet y = k === 0 ? startAt(p.y, p.startY) : p.y;\n\t\t\tif (back) y = reverseMorphContour(y);\n\t\t\tif (p.point) {\n\t\t\t\txs.push(x);\n\t\t\t\tys.push(y);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst pinned = k === 0 && explicit;\n\t\t\t// The closed side turns: `b` when it is closed, else `a`.\n\t\t\tconst turnX = !y.closed && x.closed;\n\t\t\tif (match && !pinned && (y.closed || turnX)) {\n\t\t\t\tconst r = turnX ? bestStart(y, x, useSC) : bestStart(x, y, useSC);\n\t\t\t\tif (turnX) x = turnMorphContour(x, r.start);\n\t\t\t\telse y = turnMorphContour(y, r.start);\n\t\t\t\tcost += r.cost;\n\t\t\t} else cost += bestStart(x, { ...y, closed: false }, useSC).cost;\n\t\t\txs.push(x);\n\t\t\tys.push(y);\n\t\t});\n\t\tif (!best || cost < best.cost - 1e-9) best = { xs, ys, cost, back };\n\t}\n\treturn best ? { a: best.xs, b: best.ys, reversed: best.back } : { a: [], b: [], reversed: false };\n}\n\n/**\n * How many pairs of straight anchor paths (from `a`'s anchor i to `b`'s anchor i) cross: a\n * sanity measure of a correspondence — good matches don't make anchors pass through each other.\n */\nexport function morphPathsCross(a: readonly VectorMorphContour[], b: readonly VectorMorphContour[]): number {\n\tlet count = 0;\n\tconst d = (p: Point, q: Point, r: Point) => (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0]);\n\tfor (let k = 0; k < Math.min(a.length, b.length); k++) {\n\t\tconst pa = anchorPoints(a[k]);\n\t\tconst pb = anchorPoints(b[k]);\n\t\tconst n = Math.min(pa.length, pb.length);\n\t\tfor (let i = 0; i < n; i++)\n\t\t\tfor (let j = i + 1; j < n; j++) {\n\t\t\t\tconst d1 = d(pa[j], pb[j], pa[i]);\n\t\t\t\tconst d2 = d(pa[j], pb[j], pb[i]);\n\t\t\t\tconst d3 = d(pa[i], pb[i], pa[j]);\n\t\t\t\tconst d4 = d(pa[i], pb[i], pb[j]);\n\t\t\t\tif (d1 * d2 < 0 && d3 * d4 < 0) count++;\n\t\t\t}\n\t}\n\treturn count;\n}\n\n// ---------------------------------------------------------------------------\n// In-betweens\n// ---------------------------------------------------------------------------\n\n/** The similarity (rotation θ, scale s, centroids) that best maps `a`'s anchors onto `b`'s. */\ninterface Similarity {\n\tca: Point;\n\tcb: Point;\n\ttheta: number;\n\ts: number;\n}\n\nfunction procrustes(a: readonly VectorMorphContour[], b: readonly VectorMorphContour[]): Similarity {\n\tconst pa = a.flatMap(anchorPoints);\n\tconst pb = b.flatMap(anchorPoints);\n\tconst ca = frame(pa).c;\n\tconst cb = frame(pb).c;\n\tlet ss = 0;\n\tlet dot = 0;\n\tlet cross = 0;\n\tfor (let i = 0; i < Math.min(pa.length, pb.length); i++) {\n\t\tconst p: Point = [pa[i][0] - ca[0], pa[i][1] - ca[1]];\n\t\tconst q: Point = [pb[i][0] - cb[0], pb[i][1] - cb[1]];\n\t\tss += p[0] * p[0] + p[1] * p[1];\n\t\tdot += p[0] * q[0] + p[1] * q[1];\n\t\tcross += p[0] * q[1] - p[1] * q[0];\n\t}\n\treturn { ca, cb, theta: Math.atan2(cross, dot), s: ss > 1e-12 ? Math.hypot(dot, cross) / ss : 1 };\n}\n\n/**\n * Corresponded contours at `u`: each point carried by the similarity interpolated to `u`\n * (rotation θu, scale sᵘ, centroid moving straight), plus `u` of what the full similarity\n * leaves over, blended with the straight path by `rigidity`.\n */\nexport function interpolateMorphContours(\n\ta: readonly VectorMorphContour[],\n\tb: readonly VectorMorphContour[],\n\tu: number,\n\toptions: Pick<VectorMorphOptions, 'linear' | 'rigidity'> & { similarity?: Similarity } = {}\n): VectorMorphContour[] {\n\tconst w = options.linear ? 0 : Math.min(1, Math.max(0, options.rigidity ?? 1));\n\tconst sim = w > 0 ? (options.similarity ?? procrustes(a, b)) : null;\n\tconst rot = (p: Point, ang: number, k: number): Point => {\n\t\tconst c = Math.cos(ang);\n\t\tconst s = Math.sin(ang);\n\t\treturn [k * (c * p[0] - s * p[1]), k * (s * p[0] + c * p[1])];\n\t};\n\tconst thetaU = sim ? sim.theta * u : 0;\n\tconst sU = sim ? Math.max(1e-9, sim.s) ** u : 1;\n\tconst cU = sim ? lerpP(sim.ca, sim.cb, u) : ([0, 0] as Point);\n\treturn a.map((ca, k) => {\n\t\tconst cb = b[k];\n\t\tconst pts = ca.pts.map((p, i) => {\n\t\t\tconst q = cb.pts[i] ?? p;\n\t\t\tconst linear = lerpP(p, q, u);\n\t\t\tif (!sim) return linear;\n\t\t\tconst d: Point = [p[0] - sim.ca[0], p[1] - sim.ca[1]];\n\t\t\tconst full = rot(d, sim.theta, sim.s);\n\t\t\tconst resid: Point = [q[0] - (full[0] + sim.cb[0]), q[1] - (full[1] + sim.cb[1])];\n\t\t\tconst part = rot(d, thetaU, sU);\n\t\t\tconst rigid: Point = [part[0] + cU[0] + resid[0] * u, part[1] + cU[1] + resid[1] * u];\n\t\t\treturn lerpP(linear, rigid, w);\n\t\t});\n\t\treturn { closed: ca.closed && cb.closed, pts };\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Transforms\n// ---------------------------------------------------------------------------\n\n/**\n * Interpolates two rigid matrices as a rotation (the shorter way round) plus a translation.\n * Any scale or skew is interpolated linearly in the rotated frame.\n */\nexport function interpolateRigidMatrix(a: AffineMatrix, b: AffineMatrix, t: number): AffineMatrix {\n\tconst decompose = (m: AffineMatrix) => {\n\t\tconst sx = Math.hypot(m[0], m[1]);\n\t\tconst ang = sx > 1e-12 ? Math.atan2(m[1], m[0]) : 0;\n\t\tconst c = Math.cos(ang);\n\t\tconst s = Math.sin(ang);\n\t\treturn { e: m[4], f: m[5], ang, sx, sh: c * m[2] + s * m[3], sy: -s * m[2] + c * m[3] };\n\t};\n\tconst p = decompose(a);\n\tconst q = decompose(b);\n\tlet turn = q.ang - p.ang;\n\tturn = ((((turn + Math.PI) % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI)) - Math.PI;\n\tconst ang = p.ang + turn * t;\n\tconst sx = lerpN(p.sx, q.sx, t);\n\tconst sh = lerpN(p.sh, q.sh, t);\n\tconst sy = lerpN(p.sy, q.sy, t);\n\tconst c = Math.cos(ang);\n\tconst s = Math.sin(ang);\n\treturn [c * sx, s * sx, c * sh - s * sy, s * sh + c * sy, lerpN(p.e, q.e, t), lerpN(p.f, q.f, t)];\n}\n\n// ---------------------------------------------------------------------------\n// Paint\n// ---------------------------------------------------------------------------\n\n/** Two colours mixed in OKLab (premultiplied alpha); unreadable colours switch halfway. */\nexport function interpolateVectorColor(a: string, b: string, t: number): string {\n\tif (t <= 0) return a;\n\tif (t >= 1) return b;\n\treturn mixColorsOklab(a, b, t) ?? (t < 0.5 ? a : b);\n}\n\n/**\n * A gradient's stops resampled at `offsets`, each painting what the gradient paints there (its\n * midpoints, smoothness and interpolation space included).\n */\nexport function resampleGradientStops(g: VectorGradient, offsets: readonly number[]): VectorGradientStop[] {\n\treturn offsets.map((offset) => {\n\t\tconst s = sampleVectorGradient(g, offset);\n\t\treturn s.opacity < 1 ? { offset, color: s.color, opacity: Math.round(s.opacity * 1e4) / 1e4 } : { offset, color: s.color };\n\t});\n}\n\nfunction lerpAngle(a: number, b: number, t: number): number {\n\tconst d = ((((b - a + 180) % 360) + 360) % 360) - 180;\n\treturn a + d * t;\n}\n\nconst lerpPoint = (a: Vec2, b: Vec2, t: number): Vec2 => ({ x: lerpN(a.x, b.x, t), y: lerpN(a.y, b.y, t) });\nconst sameColourModel = (a: VectorGradient, b: VectorGradient) => (a.interpolation ?? 'srgb') === (b.interpolation ?? 'srgb') && (a.smoothness ?? 0) === (b.smoothness ?? 0);\n\n/** Stops of two gradients mixed: stop by stop (midpoints too) when they match, else resampled to a common set. */\nfunction lerpStops(a: VectorGradient, b: VectorGradient, t: number): { stops: VectorGradientStop[]; resampled: boolean } {\n\tlet stopsA = sortedVectorGradientStops(a);\n\tlet stopsB = sortedVectorGradientStops(b);\n\tconst resampled = stopsA.length !== stopsB.length || !sameColourModel(a, b);\n\tif (resampled) {\n\t\t// Where either gradient bends (its own stops and the extra stops its midpoints and colour space need).\n\t\tconst offs = [...expandVectorGradientStops(a), ...expandVectorGradientStops(b), ...stopsA, ...stopsB].map((s) => s.offset).filter(Number.isFinite).sort((x, y) => x - y);\n\t\tconst common = offs.filter((o, i) => i === 0 || o - offs[i - 1] > 1e-4);\n\t\tstopsA = resampleGradientStops(a, common);\n\t\tstopsB = resampleGradientStops(b, common);\n\t}\n\tconst stops = stopsA.map((x, i) => {\n\t\tconst y = stopsB[i];\n\t\tconst opacity = lerpN(x.opacity ?? 1, y.opacity ?? 1, t);\n\t\tconst stop: VectorGradientStop = { offset: lerpN(x.offset, y.offset, t), color: interpolateVectorColor(x.color, y.color, t) };\n\t\tif (opacity < 1) stop.opacity = opacity;\n\t\tconst mid = lerpN(x.midpoint ?? 0.5, y.midpoint ?? 0.5, t);\n\t\tif (Math.abs(mid - 0.5) > 1e-6) stop.midpoint = mid;\n\t\treturn stop;\n\t});\n\treturn { stops, resampled };\n}\n\n/**\n * Two gradients of one style: stops mixed, then the placement — the shorthand angle / centre /\n * radius, or the start, end, aspect and focal points when both carry them in the same units\n * (one in the box and one in the node's space switch halfway). Null for different styles.\n */\nfunction lerpGradient(a: VectorGradient, b: VectorGradient, t: number): VectorGradient | null {\n\tif (a.type !== b.type) return null;\n\tconst { stops, resampled } = lerpStops(a, b, t);\n\tconst near = t < 0.5 ? a : b;\n\tconst out: VectorGradient = { type: a.type, stops };\n\tif (!resampled) {\n\t\tif (a.interpolation && a.interpolation !== 'srgb') out.interpolation = a.interpolation;\n\t\tif (a.smoothness) out.smoothness = a.smoothness;\n\t}\n\tif (near.spread && near.spread !== 'pad') out.spread = near.spread;\n\tif (near.strokeMode && near.strokeMode !== 'within') out.strokeMode = near.strokeMode;\n\tconst ea = hasExplicitGradientGeometry(a);\n\tconst eb = hasExplicitGradientGeometry(b);\n\tif (!ea && !eb) {\n\t\tif (a.angle !== undefined || b.angle !== undefined || a.type === 'linear' || a.type === 'reflected') {\n\t\t\tconst fallback = a.type === 'linear' || a.type === 'reflected' ? 180 : 90;\n\t\t\tout.angle = lerpAngle(a.angle ?? fallback, b.angle ?? fallback, t);\n\t\t}\n\t\tif (a.type !== 'linear' && a.type !== 'reflected') {\n\t\t\tout.cx = lerpN(a.cx ?? 0.5, b.cx ?? 0.5, t);\n\t\t\tout.cy = lerpN(a.cy ?? 0.5, b.cy ?? 0.5, t);\n\t\t\tout.r = lerpN(a.r ?? 0.5, b.r ?? 0.5, t);\n\t\t}\n\t\treturn out;\n\t}\n\tconst pa = vectorGradientPoints(a);\n\tconst pb = vectorGradientPoints(b);\n\tif (pa.units !== pb.units) {\n\t\tconst src = explicitVectorGradient(near);\n\t\treturn { ...src, stops: out.stops, ...(out.interpolation ? { interpolation: out.interpolation } : {}), ...(out.smoothness ? { smoothness: out.smoothness } : {}) };\n\t}\n\tout.start = lerpPoint(pa.start, pb.start, t);\n\tout.end = lerpPoint(pa.end, pb.end, t);\n\tout.width = lerpPoint(pa.width, pb.width, t);\n\tout.units = pa.units;\n\tif (a.type === 'radial') {\n\t\tif (pa.focal || pb.focal) out.focal = lerpPoint(pa.focal ?? pa.start, pb.focal ?? pb.start, t);\n\t\tconst fr = lerpN(pa.focalRadius, pb.focalRadius, t);\n\t\tif (fr > 0) out.focalRadius = fr;\n\t}\n\treturn out;\n}\n\n/** A gradient shaped like `like` painting one colour (or nothing). */\nfunction solidLike(color: string | null, like: VectorGradient): VectorGradient {\n\treturn { ...like, stops: like.stops.map((s) => (color === null ? { offset: s.offset, color: s.color, opacity: 0 } : { offset: s.offset, color })) };\n}\n\n/**\n * Interpolates paints: colours in OKLab, gradients of one kind with their stops resampled to a\n * common set, a colour and a gradient through a gradient of that colour, and a missing paint\n * fading from transparent. Gradients of different kinds switch halfway.\n */\nexport function interpolateVectorPaint(a: VectorPaint | undefined, b: VectorPaint | undefined, t: number): VectorPaint | undefined {\n\tif (t <= 0) return a;\n\tif (t >= 1) return b;\n\tconst pa = hasPaint(a);\n\tconst pb = hasPaint(b);\n\tif (!pa && !pb) return t < 0.5 ? a : b;\n\tif (typeof a === 'string' && pa && typeof b === 'string' && pb) return interpolateVectorColor(a, b, t);\n\tif (!pa && typeof b === 'string') return interpolateVectorColor('transparent', b, t);\n\tif (!pb && typeof a === 'string') return interpolateVectorColor(a, 'transparent', t);\n\tconst ga = pa && isVectorGradient(a) ? a : null;\n\tconst gb = pb && isVectorGradient(b) ? b : null;\n\tif (ga && gb) return lerpGradient(ga, gb, t) ?? (t < 0.5 ? a : b);\n\tif (gb) return lerpGradient(solidLike(pa ? (a as string) : null, gb), gb, t) ?? b;\n\tif (ga) return lerpGradient(ga, solidLike(pb ? (b as string) : null, ga), t) ?? a;\n\treturn t < 0.5 ? a : b;\n}\n\n/** Width profiles at every position either has a point (no profile is uniform). */\nexport function interpolateWidthProfiles(a: readonly VectorWidthPoint[] | null | undefined, b: readonly VectorWidthPoint[] | null | undefined, t: number): VectorWidthPoint[] | null {\n\tif (!a?.length && !b?.length) return null;\n\tconst at = [...(a ?? []), ...(b ?? []), { t: 0 }, { t: 1 }].map((p) => p.t).sort((x, y) => x - y);\n\tconst ts = at.filter((v, i) => i === 0 || v - at[i - 1] > 1e-6);\n\treturn ts.map((pos) => {\n\t\tconst x = widthProfileAt(a, pos);\n\t\tconst y = widthProfileAt(b, pos);\n\t\treturn { t: pos, left: lerpN(x.left, y.left, t), right: lerpN(x.right, y.right, t) };\n\t});\n}\n\n/** Dash patterns: equal lengths mix; a solid stroke counts as the other's pattern with its gaps closed. */\nfunction lerpDashes(a: number[] | undefined, b: number[] | undefined, t: number): number[] | undefined {\n\tconst dashed = (d: number[] | undefined) => !!d?.length && d.some((v) => v > 0);\n\tif (!dashed(a) && !dashed(b)) return t < 0.5 ? a : b;\n\tconst even = (p: number[]) => (p.length % 2 ? [...p, ...p] : p);\n\tconst solid = (p: number[]) => {\n\t\tconst e = even(p);\n\t\tconst out: number[] = [];\n\t\tfor (let i = 0; i < e.length; i += 2) out.push(e[i] + e[i + 1], 0);\n\t\treturn out;\n\t};\n\tconst x = dashed(a) ? even(a!) : solid(b!);\n\tconst y = dashed(b) ? even(b!) : solid(a!);\n\tif (x.length !== y.length) return t < 0.5 ? a : b;\n\treturn x.map((v, i) => lerpN(v, y[i], t));\n}\n\nconst SWITCHED_KEYS = ['strokeLinecap', 'strokeLinejoin', 'fillRule', 'strokeAlign', 'startArrow', 'endArrow', 'arrowAlign'] as const;\n\n/**\n * Interpolated style of two paths: paints, stroke width, dashes, arrowhead scales and width\n * profiles mix; caps, joins, the fill rule, alignment and arrowheads switch halfway.\n */\nexport function interpolateVectorStyle(a: VectorStyle, b: VectorStyle, t: number): VectorStyle {\n\tconst near = t < 0.5 ? a : b;\n\tconst out: VectorStyle = {};\n\tconst fill = interpolateVectorPaint(a.fill, b.fill, t);\n\tif (fill !== undefined) out.fill = fill;\n\tconst stroke = interpolateVectorPaint(a.stroke, b.stroke, t);\n\tif (stroke !== undefined) out.stroke = stroke;\n\tif (a.strokeWidth !== undefined || b.strokeWidth !== undefined) out.strokeWidth = lerpN(a.strokeWidth ?? 1, b.strokeWidth ?? 1, t);\n\tfor (const key of SWITCHED_KEYS) if (near[key] !== undefined && near[key] !== null) (out as Record<string, unknown>)[key] = near[key];\n\tfor (const key of ['startArrowScale', 'endArrowScale'] as const)\n\t\tif (a[key] !== undefined || b[key] !== undefined) out[key] = lerpN(a[key] ?? 1, b[key] ?? 1, t);\n\tconst dash = lerpDashes(a.strokeDasharray, b.strokeDasharray, t);\n\tif (dash?.length) out.strokeDasharray = dash;\n\tconst profile = interpolateWidthProfiles(a.strokeWidthProfile, b.strokeWidthProfile, t);\n\tif (profile) out.strokeWidthProfile = profile;\n\treturn out;\n}\n\n/** A path's style as seen running backwards: arrowheads swap ends and width profiles reverse. */\nfunction reversedStyle(s: VectorStyle): VectorStyle {\n\tconst out: VectorStyle = { ...s };\n\tout.startArrow = s.endArrow;\n\tout.endArrow = s.startArrow;\n\tout.startArrowScale = s.endArrowScale;\n\tout.endArrowScale = s.startArrowScale;\n\tif (s.strokeWidthProfile?.length) out.strokeWidthProfile = reverseWidthProfile(s.strokeWidthProfile);\n\tfor (const k of ['startArrow', 'endArrow', 'startArrowScale', 'endArrowScale'] as const) if (out[k] === undefined) delete out[k];\n\treturn out;\n}\n\nconst STYLE_KEYS = [\n\t'fill',\n\t'stroke',\n\t'strokeWidth',\n\t'strokeLinecap',\n\t'strokeLinejoin',\n\t'strokeDasharray',\n\t'fillRule',\n\t'strokeAlign',\n\t'startArrow',\n\t'endArrow',\n\t'arrowAlign',\n\t'startArrowScale',\n\t'endArrowScale',\n\t'strokeWidthProfile'\n] as const;\n\nfunction styleOf(n: VectorPathNode): VectorStyle {\n\tconst out: VectorStyle = {};\n\tfor (const k of STYLE_KEYS) if (n[k] !== undefined) (out as Record<string, unknown>)[k] = n[k];\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Path and node morphs\n// ---------------------------------------------------------------------------\n\nfunction contoursOf(node: VectorPathNode, m: AffineMatrix): VectorMorphContour[] {\n\treturn expandVectorCorners(node.subpaths)\n\t\t.map((sp) => subpathToMorphContour(sp, m))\n\t\t.filter((c): c is VectorMorphContour => !!c);\n}\n\n/**\n * Two paths (each placed by `ma` / `mb` into a common space: by default their own\n * transforms, i.e. their parent's space) prepared for interpolation.\n */\nfunction preparePathMorph(\n\ta: VectorPathNode,\n\tb: VectorPathNode,\n\toptions: VectorMorphOptions,\n\tma: AffineMatrix = nodeMatrix(a),\n\tmb: AffineMatrix = nodeMatrix(b)\n): (t: number) => VectorPathNode {\n\tconst corr = correspondMorphContours(contoursOf(a, ma), contoursOf(b, mb), options);\n\tconst sim = procrustes(corr.a, corr.b);\n\tconst styleA = styleOf(a);\n\tconst styleB = corr.reversed ? reversedStyle(styleOf(b)) : styleOf(b);\n\tconst rigid = !isIdentityMatrix(ma) || !isIdentityMatrix(mb);\n\tconst id = options.id ?? a.id;\n\treturn (t) => {\n\t\tconst contours = interpolateMorphContours(corr.a, corr.b, t, { linear: options.linear, rigidity: options.rigidity, similarity: sim });\n\t\tconst m = rigid ? interpolateRigidMatrix(ma, mb, t) : IDENTITY_MATRIX;\n\t\tconst back = rigid ? invertMatrix(m) : IDENTITY_MATRIX;\n\t\tconst subpaths = contours.map((c, k) => {\n\t\t\t// A pair of one closed and one open contour is open in between and takes each key's state at the ends.\n\t\t\tconst closed = t <= 0 ? corr.a[k].closed : t >= 1 ? corr.b[k].closed : corr.a[k].closed && corr.b[k].closed;\n\t\t\treturn morphContourToSubpath({ closed: corr.a[k].closed && corr.b[k].closed, pts: c.pts }, back, closed);\n\t\t});\n\t\tconst node: VectorPathNode = { id, type: 'path', subpaths, ...interpolateVectorStyle(styleA, styleB, t) };\n\t\tconst opacity = lerpN(a.opacity ?? 1, b.opacity ?? 1, t);\n\t\tif (opacity < 1) node.opacity = opacity;\n\t\tif (rigid && !isIdentityMatrix(m)) node.transform = m;\n\t\treturn node;\n\t};\n}\n\n/** Path nodes of a subtree with their matrices into the subtree's parent space. */\nfunction flattenPaths(node: VectorNode, parent: AffineMatrix = IDENTITY_MATRIX): { path: VectorPathNode; m: AffineMatrix; opacity: number }[] {\n\tconst m = multiplyMatrices(nodeMatrix(node), parent);\n\tif (node.type === 'path') return [{ path: node, m, opacity: 1 }];\n\tconst o = node.opacity ?? 1;\n\treturn node.children\n\t\t.filter((c) => c.visible !== false)\n\t\t.flatMap((c) => flattenPaths(c, m))\n\t\t.map((e) => ({ ...e, opacity: e.opacity * o }));\n}\n\n/** Bounds of a path placed by `m`. */\nconst placedBounds = (path: VectorPathNode, m: AffineMatrix) => boundsOfPoints(contoursOf(path, m).flatMap((c) => c.pts));\n\n/** A copy of `path` shrunk to the point that matches it in the other shape's box (placed by the identity). */\nfunction collapsedPath(path: VectorPathNode, m: AffineMatrix, from: VectorBounds | null, to: VectorBounds | null): VectorPathNode {\n\tconst own = placedBounds(path, m);\n\tconst c: Point = own ? [own.x + own.width / 2, own.y + own.height / 2] : [0, 0];\n\tconst fx = from && from.width > 0 ? (c[0] - from.x) / from.width : 0.5;\n\tconst fy = from && from.height > 0 ? (c[1] - from.y) / from.height : 0.5;\n\tconst p = to ? { x: to.x + to.width * fx, y: to.y + to.height * fy } : { x: c[0], y: c[1] };\n\tconst subpaths = path.subpaths.map((sp) => ({ closed: sp.closed, anchors: sp.anchors.map(() => ({ x: p.x, y: p.y })) }));\n\tconst { transform: _t, ...rest } = path;\n\treturn { ...rest, subpaths };\n}\n\n/**\n * Prepares two shapes for interpolation. Two paths morph directly; with a group on either side\n * the paths inside pair up in stacking order and those without a partner grow out of a point.\n * Shapes are blended in their parent's space.\n */\nexport function createVectorMorph(a: VectorNode, b: VectorNode, options: VectorMorphOptions = {}): VectorMorph {\n\tif (a.type === 'path' && b.type === 'path') {\n\t\tconst at = preparePathMorph(a, b, options);\n\t\treturn { at };\n\t}\n\tconst pa = flattenPaths(a);\n\tconst pb = flattenPaths(b);\n\tconst ba = unionBounds(pa.map((e) => placedBounds(e.path, e.m)));\n\tconst bb = unionBounds(pb.map((e) => placedBounds(e.path, e.m)));\n\tconst id = options.id ?? a.id;\n\tconst parts: ((t: number) => VectorPathNode)[] = [];\n\tconst opacities: [number, number][] = [];\n\tfor (let i = 0; i < Math.max(pa.length, pb.length); i++) {\n\t\tconst x = pa[i];\n\t\tconst y = pb[i];\n\t\tconst sub = { ...options, id: `${id}-${i}`, starts: undefined };\n\t\tif (x && y) {\n\t\t\tparts.push(preparePathMorph(x.path, y.path, sub, x.m, y.m));\n\t\t\topacities.push([x.opacity, y.opacity]);\n\t\t} else if (x) {\n\t\t\tparts.push(preparePathMorph(x.path, collapsedPath(x.path, x.m, ba, bb), sub, x.m, IDENTITY_MATRIX));\n\t\t\topacities.push([x.opacity, x.opacity]);\n\t\t} else if (y) {\n\t\t\tparts.push(preparePathMorph(collapsedPath(y.path, y.m, bb, ba), y.path, sub, IDENTITY_MATRIX, y.m));\n\t\t\topacities.push([y.opacity, y.opacity]);\n\t\t}\n\t}\n\treturn {\n\t\tat: (t) => {\n\t\t\tconst children = parts.map((p, i) => {\n\t\t\t\tconst n = p(t);\n\t\t\t\tconst o = lerpN(opacities[i][0], opacities[i][1], t) * (n.opacity ?? 1);\n\t\t\t\tif (o < 1) n.opacity = o;\n\t\t\t\telse delete n.opacity;\n\t\t\t\treturn n;\n\t\t\t});\n\t\t\tconst group: VectorGroupNode = { id, type: 'group', children };\n\t\t\treturn group;\n\t\t}\n\t};\n}\n\n/**\n * The in-between of two shapes at `t` (0 = `a`, 1 = `b`). Prepare once with\n * `createVectorMorph` when interpolating the same pair at many `t`.\n */\nexport function interpolateVectorPaths(a: VectorNode, b: VectorNode, t: number, options: VectorMorphOptions = {}): VectorNode {\n\treturn createVectorMorph(a, b, options).at(t);\n}\n\n/** A keyframe of a path track: a shape at a time (ms), with the easing of the segment leaving it. */\nexport interface VectorPathKeyframe {\n\ttime: number;\n\tvalue: VectorNode;\n\teasing?: AnimationEasing;\n}\n\nconst keyframeMorphs = new WeakMap<VectorNode, WeakMap<VectorNode, VectorMorph>>();\n\n/**\n * A path track's shape at `time`: before the first keyframe its shape, after the last the\n * last; in between the two keyframes morph (prepared once per pair and cached) at the eased\n * fraction. Easings that overshoot are clamped to the keys. `null` without keyframes.\n */\nexport function evaluateVectorPathKeyframes(keyframes: readonly VectorPathKeyframe[], time: number, options: VectorMorphOptions = {}): VectorNode | null {\n\tconst keys = keyframes.filter((k) => Number.isFinite(k.time)).sort((x, y) => x.time - y.time);\n\tif (!keys.length) return null;\n\tif (!(time > keys[0].time)) return keys[0].value;\n\tconst last = keys[keys.length - 1];\n\tif (time >= last.time) return last.value;\n\tlet i = 0;\n\twhile (i + 1 < keys.length && keys[i + 1].time <= time) i++;\n\tconst a = keys[i];\n\tconst b = keys[i + 1];\n\tif (time === a.time) return a.value;\n\tif (a.easing === 'hold') return a.value;\n\tconst span = b.time - a.time;\n\tconst u = Math.min(1, Math.max(0, evaluateEasing(a.easing, (time - a.time) / span, span)));\n\tlet inner = keyframeMorphs.get(a.value);\n\tif (!inner) keyframeMorphs.set(a.value, (inner = new WeakMap()));\n\tlet morph = inner.get(b.value);\n\tif (!morph) inner.set(b.value, (morph = createVectorMorph(a.value, b.value, options)));\n\treturn morph.at(u);\n}\n","/**\n * Gradients laid along or across a stroke (`strokeMode: 'along' | 'across'`), as a `<pattern>`\n * paint server of slices, so the canvases and exported SVG draw them with plain SVG.\n *\n * Each subpath is flattened and cut into slices, one per flattened segment, reaching past\n * everything the stroke paints there (its joins, caps and arrowheads). Gentle turns share a rib on\n * the bisector, so neighbouring slices meet where both give the same colour; a sharp corner gets\n * square ribs and a patch under them in the corner's colour, and the open ends get a patch in the\n * end colour (caps and heads). Along: a slice's linear gradient runs from its start to its end\n * with the part of the gradient between their positions (by length, 0–1 per subpath), padded\n * beyond; across: from the stroke's left edge to its right edge (the visible band of an inside or\n * outside stroke; a width profile's sides at the slice's middle). The pattern paints only where the\n * stroke does, so no clipping is needed. Each slice overlaps the next a little to hide\n * anti-aliasing seams.\n *\n * After vectorcraft `crates/effects/src/stroke/gradient.rs` (ribs on the bisectors of gentle\n * turns, a fan round sharp corners: here one patch), ArtCraft, MIT OR Apache-2.0 — see\n * THIRD_PARTY_NOTICES.md.\n */\nimport type { SvgGradientDef } from '../canvas/gradient.js';\nimport { closeContour, flattenChain, pathToContours } from '../canvas/pathops/bezier.js';\nimport type { Point } from '../canvas/types.js';\nimport { subpathsToBezier } from './geometry.js';\nimport { strokeWidthProfileOf, vectorStrokeReach } from './stroke.js';\nimport { widthProfileAt } from './width.js';\nimport {\n\texpandVectorGradientStops,\n\tpatternAttrs,\n\tspanVectorGradientStops,\n\tvectorGradientArea,\n\ttype VectorPaintDef,\n\ttype VectorSvgPatternDef\n} from './gradient.js';\nimport type { Vec2, VectorBounds, VectorGradient, VectorGradientStop, VectorPathNode } from './types.js';\n\n/** Largest stretch of a rib on a turn's bisector (1 / cos of half the turn, about 74°) before the corner gets a patch. */\nconst SHARP = 1.25;\n/** Flattening tolerance (node units). */\nconst TOLERANCE = 0.2;\n/** How far each slice reaches into the next (node units). */\nconst OVERLAP = 0.5;\n\nconst fmt = (n: number, digits = 3) => {\n\tconst k = 10 ** digits;\n\tconst v = Math.round(n * k) / k;\n\treturn Object.is(v, -0) ? '0' : String(v);\n};\nconst toSvgStops = (stops: readonly VectorGradientStop[]) =>\n\tstops.map((s) => ({ offset: `${fmt(s.offset * 100)}%`, stopColor: s.color, ...(s.opacity !== undefined && s.opacity !== 1 ? { stopOpacity: Math.round(s.opacity * 1e4) / 1e4 } : {}) }));\nconst polygonD = (pts: readonly Vec2[], precision: number) => `M${pts.map((p) => `${fmt(p.x, precision)} ${fmt(p.y, precision)}`).join('L')}Z`;\nconst add = (a: Vec2, b: Vec2): Vec2 => ({ x: a.x + b.x, y: a.y + b.y });\nconst sub = (a: Vec2, b: Vec2): Vec2 => ({ x: a.x - b.x, y: a.y - b.y });\nconst scale = (a: Vec2, k: number): Vec2 => ({ x: a.x * k, y: a.y * k });\n/** Left of direction `d` looking along it on screen (y down). */\nconst left = (d: Vec2): Vec2 => ({ x: d.y, y: -d.x });\nconst unit = (v: Vec2): Vec2 => {\n\tconst l = Math.hypot(v.x, v.y);\n\treturn l > 1e-12 ? { x: v.x / l, y: v.y / l } : { x: 1, y: 0 };\n};\n\n/** A cross-section of the strip: from `l` (left of the path) through the path to `r`. */\ninterface Rib {\n\tl: Vec2;\n\tr: Vec2;\n}\n\n/** The flattened subpaths of a path (node space): points, whether closed, and their lengths. */\nfunction flattenedSubpaths(node: VectorPathNode): { pts: Vec2[]; closed: boolean }[] {\n\tconst out: { pts: Vec2[]; closed: boolean }[] = [];\n\tfor (const contour of pathToContours(subpathsToBezier(node.subpaths))) {\n\t\tconst segs = closeContour(contour);\n\t\tif (!segs.length) continue;\n\t\tconst ring: Point[] = flattenChain(segs, TOLERANCE);\n\t\tconst pts = ring.map(([x, y]) => ({ x, y })).filter((p, i, a) => i === 0 || Math.hypot(p.x - a[i - 1].x, p.y - a[i - 1].y) > 1e-9);\n\t\tif (contour.closed && pts.length > 2 && Math.hypot(pts[0].x - pts[pts.length - 1].x, pts[0].y - pts[pts.length - 1].y) < 1e-9) pts.pop();\n\t\tif (pts.length >= 2) out.push({ pts, closed: contour.closed && pts.length > 2 });\n\t}\n\treturn out;\n}\n\n/** Shoelace area (positive: clockwise on screen, y down). */\nconst signedArea = (pts: readonly Vec2[]) => {\n\tlet a = 0;\n\tfor (let i = 0; i < pts.length; i++) {\n\t\tconst p = pts[i];\n\t\tconst q = pts[(i + 1) % pts.length];\n\t\ta += p.x * q.y - q.x * p.y;\n\t}\n\treturn a / 2;\n};\n\n/**\n * The paint server of a gradient along or across the stroke of `node` (in its own space): a\n * pattern whose first def id is `id`. `area` defaults to the path's box padded by the stroke's\n * reach.\n */\nexport function vectorStrokeGradientDefs(g: VectorGradient, id: string, node: VectorPathNode, options: { bounds?: VectorBounds | null; area?: VectorBounds | null; precision?: number } = {}): VectorPaintDef[] {\n\tconst precision = options.precision ?? 3;\n\tconst mode = g.strokeMode === 'across' ? 'across' : 'along';\n\tconst expanded = expandVectorGradientStops(g);\n\tconst reach = vectorStrokeReach(node) + 1;\n\tconst area = options.area ?? vectorGradientArea(options.bounds && { x: options.bounds.x - reach, y: options.bounds.y - reach, width: options.bounds.width + 2 * reach, height: options.bounds.height + 2 * reach });\n\tconst width = node.strokeWidth ?? 1;\n\tconst align = node.strokeAlign ?? 'center';\n\tconst profile = strokeWidthProfileOf(node);\n\tconst gradients: SvgGradientDef[] = [];\n\tconst patches: { d: string; fill: string }[] = [];\n\tconst slices: { d: string; fill: string }[] = [];\n\tconst colourAt = (t: number) => {\n\t\tconst s = spanVectorGradientStops(g, t, t, expanded)[0];\n\t\treturn s;\n\t};\n\tlet counter = 0;\n\tconst solid = (pts: Vec2[], t: number) => {\n\t\tconst s = colourAt(t);\n\t\tconst gid = `${id}-p${counter++}`;\n\t\t// A one-stop gradient keeps the stop's opacity (a fill colour would need fill-opacity).\n\t\tgradients.push({ id: gid, type: 'linear', attrs: { gradientUnits: 'userSpaceOnUse', x1: 0, y1: 0, x2: 1, y2: 0 }, stops: toSvgStops([{ ...s, offset: 0 }]) });\n\t\tpatches.push({ d: polygonD(pts, precision), fill: `url(#${gid})` });\n\t};\n\tconst disc = (c: Vec2, r: number) => Array.from({ length: 16 }, (_, k) => add(c, scale({ x: Math.cos((k * Math.PI) / 8), y: Math.sin((k * Math.PI) / 8) }, r)));\n\n\tfor (const { pts, closed } of flattenedSubpaths(node)) {\n\t\tconst n = pts.length;\n\t\tconst segCount = closed ? n : n - 1;\n\t\tconst dirs: Vec2[] = [];\n\t\tconst lengths: number[] = [0];\n\t\tfor (let i = 0; i < segCount; i++) {\n\t\t\tconst a = pts[i];\n\t\t\tconst b = pts[(i + 1) % n];\n\t\t\tdirs.push(unit(sub(b, a)));\n\t\t\tlengths.push(lengths[i] + Math.hypot(b.x - a.x, b.y - a.y));\n\t\t}\n\t\tconst total = lengths[segCount];\n\t\tif (!(total > 1e-9)) continue;\n\t\tconst tAt = (i: number) => lengths[i] / total;\n\t\t// Inside / outside strokes show one side: the right of a clockwise outline is its inside.\n\t\tconst clockwise = closed && signedArea(pts) > 0;\n\t\tconst visibleLeft = align === 'center' || !closed ? null : align === 'inside' ? !clockwise : clockwise;\n\n\t\t// Ribs at each vertex: `before` ends the slice arriving, `after` starts the one leaving.\n\t\tconst ribs: { before: Rib; after: Rib }[] = [];\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst p = pts[i];\n\t\t\tconst din = i > 0 || closed ? dirs[(i - 1 + segCount) % segCount] : null;\n\t\t\tconst dout = i < segCount ? dirs[i] : null;\n\t\t\tconst square = (d: Vec2): Rib => ({ l: add(p, scale(left(d), reach)), r: sub(p, scale(left(d), reach)) });\n\t\t\tif (!din || !dout) {\n\t\t\t\tconst rib = square((din ?? dout)!);\n\t\t\t\tribs.push({ before: rib, after: rib });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst nin = left(din);\n\t\t\tconst nout = left(dout);\n\t\t\tconst den = 1 + nin.x * nout.x + nin.y * nout.y;\n\t\t\tconst stretch = den > 1e-12 ? Math.sqrt(2 / den) : Infinity;\n\t\t\tif (stretch <= SHARP) {\n\t\t\t\tconst m = scale(add(nin, nout), reach / den);\n\t\t\t\tconst rib = { l: add(p, m), r: sub(p, m) };\n\t\t\t\tribs.push({ before: rib, after: rib });\n\t\t\t} else {\n\t\t\t\tribs.push({ before: square(din), after: square(dout) });\n\t\t\t\tsolid(disc(p, reach * 1.5), tAt(i));\n\t\t\t}\n\t\t}\n\t\tif (!closed) {\n\t\t\t// Caps and arrowheads beyond the ends paint the end colours.\n\t\t\tconst d0 = dirs[0];\n\t\t\tconst d1 = dirs[segCount - 1];\n\t\t\tconst a = pts[0];\n\t\t\tconst b = pts[n - 1];\n\t\t\tconst cap = (p: Vec2, d: Vec2) => [add(p, scale(left(d), reach)), add(add(p, scale(d, reach)), scale(left(d), reach)), sub(add(p, scale(d, reach)), scale(left(d), reach)), sub(p, scale(left(d), reach))];\n\t\t\tsolid(cap(a, scale(d0, -1)), 0);\n\t\t\tsolid(cap(b, d1), 1);\n\t\t}\n\t\tfor (let i = 0; i < segCount; i++) {\n\t\t\tconst j = (i + 1) % n;\n\t\t\tconst a = pts[i];\n\t\t\tconst b = pts[j];\n\t\t\tconst d = dirs[i];\n\t\t\tconst from = ribs[i].after;\n\t\t\tconst to = ribs[j].before;\n\t\t\tconst ext = scale(d, OVERLAP);\n\t\t\tconst quad = [from.l, add(to.l, ext), add(to.r, ext), from.r];\n\t\t\tconst gid = `${id}-s${counter++}`;\n\t\t\tconst t0 = tAt(i);\n\t\t\tconst t1 = closed && i === segCount - 1 ? 1 : tAt(i + 1);\n\t\t\tif (mode === 'along') {\n\t\t\t\tgradients.push({\n\t\t\t\t\tid: gid,\n\t\t\t\t\ttype: 'linear',\n\t\t\t\t\tattrs: { gradientUnits: 'userSpaceOnUse', x1: fmt(a.x, 4), y1: fmt(a.y, 4), x2: fmt(b.x, 4), y2: fmt(b.y, 4) },\n\t\t\t\t\tstops: toSvgStops(spanVectorGradientStops(g, t0, t1, expanded))\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconst mid = scale(add(a, b), 0.5);\n\t\t\t\tconst side = widthProfileAt(profile, (t0 + t1) / 2);\n\t\t\t\tconst nrm = left(d);\n\t\t\t\tconst reachL = visibleLeft === null ? (width / 2) * side.left : visibleLeft ? width * side.left : 0;\n\t\t\t\tconst reachR = visibleLeft === null ? (width / 2) * side.right : visibleLeft ? 0 : width * side.right;\n\t\t\t\tconst p0 = add(mid, scale(nrm, reachL));\n\t\t\t\tconst p1 = sub(mid, scale(nrm, reachR));\n\t\t\t\tgradients.push({\n\t\t\t\t\tid: gid,\n\t\t\t\t\ttype: 'linear',\n\t\t\t\t\tattrs: { gradientUnits: 'userSpaceOnUse', x1: fmt(p0.x, 4), y1: fmt(p0.y, 4), x2: fmt(p1.x, 4), y2: fmt(p1.y, 4) },\n\t\t\t\t\tstops: toSvgStops(expanded)\n\t\t\t\t});\n\t\t\t}\n\t\t\tslices.push({ d: polygonD(quad, precision), fill: `url(#${gid})` });\n\t\t}\n\t}\n\tconst def: VectorSvgPatternDef = {\n\t\tid,\n\t\ttype: 'pattern',\n\t\tattrs: patternAttrs(area),\n\t\tgradients,\n\t\ttiles: [...patches, ...slices]\n\t};\n\treturn [def];\n}\n","/**\n * SVG export and import for vector documents. Import is DOM-free (works in Node and\n * workers): it reads `<svg>`, `<g>`, `<path>`, `<rect>`, `<circle>`, `<ellipse>`, `<line>`,\n * `<polyline>`, `<polygon>`, linear / radial gradients, presentation attributes, `style`\n * declarations, `transform`s, `clip-path` and `mask` references (each becomes a mask group, the\n * clip or mask its first child). Text, images, foreign filters and `<use>` are skipped.\n *\n * Live corners export as their cut outline plus `data-vector-d` (the sharp path) and\n * `data-vector-corners` (the radii), so the editor's own SVG imports with editable corners.\n * Inside / outside strokes, which SVG cannot express, export (after vectorcraft\n * `crates/svg/src/lib.rs`, MIT OR Apache-2.0) as a `<g data-vector-path>` holding the fill, a\n * stroke of twice the width clipped to the shape (inside) or masked by it (outside), and the\n * arrowheads as filled outlines; import merges such a group back into one path. A variable-width\n * stroke exports as its filled outline (`data-vector-part=\"stroke-outline\"`) with the profile\n * and line style kept in `data-vector-width-profile` / `data-vector-stroke-*` attributes.\n *\n * A mask group exports as a `<g data-vector-mask>` (the settings as JSON) carrying the group's\n * transform, opacity, blend mode and filter, around a `<g data-vector-part=\"masked\">` with the\n * contents and `clip-path` / `mask`, so the group's appearance applies to the masked result. A\n * clip's `<clipPath>` holds the mask's outlines; for import, the mask itself is kept in a hidden\n * `<g data-vector-part=\"mask-source\">`. An opacity mask's `<mask>` holds the mask as drawn.\n */\nimport { bezierPathToSvg } from '../canvas/pathops/bezier.js';\nimport { bezierPathBounds } from '../canvas/pathops/bezier.js';\nimport { hasStrokeExtras, strokeWidthProfileOf, vectorStrokeLineArea, vectorStrokeOutlinePath, vectorStrokeParts, vectorStrokeReach } from './stroke.js';\nimport { sanitizeWidthProfile } from './width.js';\nimport { expandVectorAppearance, hasOwnVectorAppearance, sanitizeVectorEffects, transformVectorEffects, vectorAppearanceNodes } from './appearance.js';\nimport { isVectorMaskGroup, sanitizeVectorMask, vectorMaskRenderData } from './mask.js';\nimport { activeVectorFilters, sanitizeVectorFilters, vectorFilterMarkup, vectorFilterSvg, type VectorRenderFilter } from './filters.js';\nimport {\n\tneedsVectorPaintLayers,\n\tvectorFills,\n\tvectorStrokes,\n\tsanitizeVectorFills,\n\tsanitizeVectorStrokes,\n\tvectorBlendModeCss,\n\tvectorBlendModeFromCss,\n\tvectorPaintLayerNodes,\n\tvectorStrokeStackReach,\n\tnormalizeVectorPaintStacks,\n\twithVectorFills,\n\twithVectorStrokes\n} from './paintLayers.js';\nimport { angleToSvgLinearCoords, type SvgGradientDef } from '../canvas/gradient.js';\nimport { bezierPathBounds as pathBounds } from '../canvas/pathops/bezier.js';\nimport { isVectorGradient, sanitizeVectorGradient, vectorGradientSvgDefs, type VectorPaintDef } from './gradient.js';\nimport { vectorStrokeGradientDefs } from './gradientStroke.js';\nimport { formatNumber } from '../canvas/shapeGeometry.js';\nimport {\n\tbezierToSubpaths,\n\tdecomposeRigid,\n\tellipseSubpath,\n\thasPaint,\n\tIDENTITY_MATRIX,\n\tlineSubpath,\n\tmultiplyMatrices,\n\trectSubpath,\n\trotateMatrix,\n\tscaleMatrix,\n\tsubpathsToBezier,\n\ttransformVectorNode,\n\ttranslateMatrix,\n\tvectorPathData,\n\tunionBounds,\n\tvectorNodeBounds,\n\ttype AffineMatrix\n} from './geometry.js';\nimport {\n\tVECTOR_ARROWHEADS,\n\tVECTOR_CORNER_KINDS,\n\ttype VectorArrowhead,\n\ttype VectorBlend,\n\ttype VectorBooleanOperation,\n\ttype VectorCornerKind,\n\ttype VectorDocument,\n\ttype VectorFilter,\n\ttype VectorFillRule,\n\ttype VectorGradient,\n\ttype VectorGradientStop,\n\ttype VectorGroupNode,\n\ttype VectorEffect,\n\ttype VectorLineCap,\n\ttype VectorLineJoin,\n\ttype VectorWidthPoint,\n\ttype VectorNode,\n\ttype VectorPaint,\n\ttype VectorBounds,\n\ttype VectorPathNode,\n\ttype VectorPaintLayer,\n\ttype VectorStrokeAlign,\n\ttype VectorSubpath\n} from './types.js';\n\n// ---------------------------------------------------------------------------\n// Export\n// ---------------------------------------------------------------------------\n\nexport interface VectorSvgExportOptions {\n\t/** Decimal places for coordinates (default 3). */\n\tprecision?: number;\n\t/** Draw `document.background` as a full-size rect (default true). */\n\tbackground?: boolean;\n\t/** Include hidden nodes with `display=\"none\"` instead of leaving them out (default false). */\n\tincludeHidden?: boolean;\n\t/** Prefix for gradient ids (default `vg-`), to keep ids unique when several SVGs share a page. */\n\tidPrefix?: string;\n\t/** Indent nested elements (default true). */\n\tpretty?: boolean;\n\t/**\n\t * How strokes with an inside / outside alignment or arrowheads are written: `clip` (default)\n\t * as a double-width stroke clipped or masked by the shape plus filled heads, `outline` as\n\t * one filled path of the area the stroke paints.\n\t */\n\tstrokes?: 'clip' | 'outline';\n\t/**\n\t * Groups to write round nodes, by node id, outermost first: to move a node from outside it\n\t * or clip it (the animation editor's reveals and its Flair export use this).\n\t */\n\twrap?: Readonly<Record<string, readonly VectorSvgWrapper[]>>;\n}\n\n/** A group written round a node (see `VectorSvgExportOptions.wrap`). */\nexport interface VectorSvgWrapper {\n\tid?: string;\n\t/** An SVG `transform` attribute. */\n\ttransform?: string;\n\t/**\n\t * Clips the group through a rect (`x`, `y`, `width`, `height`) or circle (`cx`, `cy`, `r`) in\n\t * the units of the group's own box (`clipPathUnits=\"objectBoundingBox\"`: 0–1 across it).\n\t */\n\tclip?: { id: string; shape: 'rect' | 'circle'; shapeId?: string; attrs: Readonly<Record<string, number>> };\n}\n\n/** `data-vector-corners`: the live corners of subpaths as `subpath.index:radius[:kind]`, space separated. */\nexport function vectorCornersAttribute(subpaths: readonly VectorSubpath[], precision = 3): string {\n\tconst out: string[] = [];\n\tsubpaths.forEach((sp, s) =>\n\t\tsp.anchors.forEach((a, i) => {\n\t\t\tif (!a.corner || !(a.corner.radius > 0)) return;\n\t\t\tout.push(`${s}.${i}:${formatNumber(a.corner.radius, precision)}${a.corner.kind && a.corner.kind !== 'round' ? `:${a.corner.kind}` : ''}`);\n\t\t})\n\t);\n\treturn out.join(' ');\n}\n\n/** Puts `data-vector-corners` back on subpaths (malformed entries are ignored). */\nexport function applyVectorCornersAttribute(subpaths: VectorSubpath[], value: string): VectorSubpath[] {\n\tfor (const item of value.trim().split(/\\s+/)) {\n\t\tconst m = /^(\\d+)\\.(\\d+):([\\d.eE+-]+)(?::([a-z]+))?$/.exec(item);\n\t\tif (!m) continue;\n\t\tconst a = subpaths[+m[1]]?.anchors[+m[2]];\n\t\tconst radius = parseFloat(m[3]);\n\t\tif (!a || !(radius > 0)) continue;\n\t\tconst kind = VECTOR_CORNER_KINDS.includes(m[4] as VectorCornerKind) ? (m[4] as VectorCornerKind) : undefined;\n\t\ta.corner = { radius, ...(kind && kind !== 'round' ? { kind } : {}) };\n\t}\n\treturn subpaths;\n}\n\nconst escapeAttr = (value: string) => value.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n\n/** Where a paint goes: what `vectorPaintToSvg` needs to place a gradient on a shape. */\nexport interface VectorPaintSvgOptions {\n\t/** The path painted (its box places `objectBoundingBox` gradients; strokes need its geometry). */\n\tnode?: VectorPathNode;\n\t/** `stroke` lays `along` / `across` gradients on the stroke and covers its reach (default `fill`). */\n\trole?: 'fill' | 'stroke';\n\tprecision?: number;\n}\n\n/** The box of a path's drawn outline in its own space (null when it has no points). */\nexport function vectorPathLocalBounds(node: VectorPathNode): VectorBounds | null {\n\tconst b = pathBounds(subpathsToBezier(node.subpaths));\n\treturn b ? { x: b.minX, y: b.minY, width: b.maxX - b.minX, height: b.maxY - b.minY } : null;\n}\n\n/** Paint defs are recomputed only when the node (or the id / role asked for) changes. */\nconst paintCache = new WeakMap<object, Map<string, VectorPaintDef[]>>();\n\n/**\n * A paint as an SVG attribute value plus the paint servers it needs (`url(#id)` refers to the\n * first). Gradients sit in the painted element's user space (the node's own space).\n */\nexport function vectorPaintToSvg(paint: VectorPaint | undefined, gradientId: string, options: VectorPaintSvgOptions = {}): { value: string; defs: VectorPaintDef[] } {\n\tif (!hasPaint(paint)) return { value: 'none', defs: [] };\n\tif (typeof paint === 'string') return { value: paint, defs: [] };\n\tif (!isVectorGradient(paint)) return { value: 'none', defs: [] };\n\tconst role = options.role ?? 'fill';\n\tconst precision = options.precision ?? 3;\n\tconst key = `${gradientId}|${role}|${precision}`;\n\tconst owner: object = options.node ?? paint;\n\tconst cached = paintCache.get(owner)?.get(key);\n\tif (cached && (!options.node || (role === 'fill' ? options.node.fill : options.node.stroke) === paint)) return { value: `url(#${gradientId})`, defs: cached };\n\tconst node = options.node;\n\tconst bounds = node ? vectorPathLocalBounds(node) : null;\n\tconst reach = node && role === 'stroke' ? vectorStrokeReach(node) + 1 : 1;\n\tconst area = bounds ? { x: bounds.x - reach, y: bounds.y - reach, width: bounds.width + 2 * reach, height: bounds.height + 2 * reach } : null;\n\tconst defs =\n\t\tnode && role === 'stroke' && (paint.strokeMode === 'along' || paint.strokeMode === 'across')\n\t\t\t? vectorStrokeGradientDefs(paint, gradientId, node, { bounds, area, precision })\n\t\t\t: vectorGradientSvgDefs(paint, gradientId, { bounds, area, precision });\n\tlet byKey = paintCache.get(owner);\n\tif (!byKey) paintCache.set(owner, (byKey = new Map()));\n\tbyKey.set(key, defs);\n\treturn { value: `url(#${gradientId})`, defs };\n}\n\n/** The parts of an aligned or arrowed stroke (see `VectorRenderPath.strokeParts`). */\nexport interface VectorRenderStrokeParts {\n\talign: VectorStrokeAlign;\n\t/** The line the stroke follows (shortened under `tip`-aligned heads). */\n\td: string;\n\t/** Width to draw it with: twice the stroke width when aligned, half of it being clipped away. */\n\twidth: number;\n\t/**\n\t * A variable-width stroke: the area to fill with `stroke` (nonzero) instead of stroking `d`,\n\t * clipped or masked like the stroke would be.\n\t */\n\toutline?: string;\n\t/**\n\t * Inside: a `<clipPath id>` holding the shape (`d`, `clip-rule` = `fillRule`). Outside: a\n\t * `<mask id>` in user space over `bounds`, white with the shape (`d`) in black.\n\t */\n\tclip: { kind: 'clip' | 'mask'; id: string; bounds: VectorBounds } | null;\n\t/** Arrowhead outlines (fill with `stroke`, nonzero). */\n\theads: string[];\n}\n\n/** Stroke parts are recomputed only when the node (or the id / precision asked for) changes. */\nconst partsCache = new WeakMap<VectorPathNode, { clipId: string; precision: number; parts: VectorRenderStrokeParts | undefined }>();\n\n/** The stroke parts of a path whose stroke needs more than one element, in its own space (undefined otherwise). */\nexport function vectorStrokeSvgParts(node: VectorPathNode, clipId: string, precision = 3): VectorRenderStrokeParts | undefined {\n\tif (!hasPaint(node.stroke) || !hasStrokeExtras(node)) return undefined;\n\tconst hit = partsCache.get(node);\n\tif (hit && hit.clipId === clipId && hit.precision === precision) return hit.parts;\n\tconst parts = computeStrokeSvgParts(node, clipId, precision);\n\tpartsCache.set(node, { clipId, precision, parts });\n\treturn parts;\n}\n\nfunction computeStrokeSvgParts(node: VectorPathNode, clipId: string, precision: number): VectorRenderStrokeParts | undefined {\n\tconst path = subpathsToBezier(node.subpaths);\n\tconst parts = vectorStrokeParts(path, node);\n\tconst profiled = !!strokeWidthProfileOf(node);\n\tif (parts.align === 'center' && !parts.heads.length && !profiled) return undefined;\n\tlet clip: VectorRenderStrokeParts['clip'] = null;\n\tif (parts.align !== 'center') {\n\t\tconst b = bezierPathBounds(path);\n\t\tconst pad = vectorStrokeReach(node) + 1;\n\t\tconst bounds = b ? { x: round2(b.minX - pad), y: round2(b.minY - pad), width: round2(b.maxX - b.minX + 2 * pad), height: round2(b.maxY - b.minY + 2 * pad) } : { x: 0, y: 0, width: 0, height: 0 };\n\t\tclip = { kind: parts.align === 'inside' ? 'clip' : 'mask', id: clipId, bounds };\n\t}\n\treturn {\n\t\talign: parts.align,\n\t\td: bezierPathToSvg(parts.line, { precision }),\n\t\twidth: parts.drawWidth,\n\t\t...(profiled && (node.strokeWidth ?? 1) > 0 ? { outline: bezierPathToSvg(vectorStrokeLineArea(parts, node), { precision }) } : {}),\n\t\tclip,\n\t\theads: parts.heads.map((h) => bezierPathToSvg(h.path, { precision }))\n\t};\n}\n\n/**\n * The box a node draws over in its own space (its `transform` not applied): its geometry as drawn\n * (effects applied), padded by how far its strokes reach. Null when it draws nothing.\n */\nexport function vectorNodeLocalPaintBounds(node: VectorNode): VectorBounds | null {\n\tconst drawn = expandVectorAppearance(node);\n\tconst reach = (n: VectorNode): number => (n.type === 'path' ? vectorStrokeStackReach(n) : Math.max(0, ...n.children.map(reach)));\n\tlet b: VectorBounds | null;\n\tif (drawn.type === 'path') b = vectorPathLocalBounds(drawn);\n\telse b = unionBounds(drawn.children.filter((c) => c.visible !== false).map((c) => vectorNodeBounds(c, IDENTITY_MATRIX)));\n\tif (!b) return null;\n\tconst pad = reach(drawn);\n\treturn { x: b.x - pad, y: b.y - pad, width: b.width + 2 * pad, height: b.height + 2 * pad };\n}\n\nconst filterCache = new WeakMap<VectorNode, { id: string; filter: VectorRenderFilter | null }>();\n\n/** A node's filter (`node.filters`) as render data with id `id`, or null when none draws. Cached per node. */\nexport function vectorNodeFilterSvg(node: VectorNode, id: string): VectorRenderFilter | null {\n\tif (!node.filters?.length) return null;\n\tconst hit = filterCache.get(node);\n\tif (hit && hit.id === id) return hit.filter;\n\tconst active = activeVectorFilters(node);\n\tconst filter = active.length ? vectorFilterSvg(active, id, vectorNodeLocalPaintBounds(node)) : null;\n\tfilterCache.set(node, { id, filter });\n\treturn filter;\n}\n\n/** A width profile as a data attribute: `t,left,right` points separated by `;`. */\nexport function widthProfileToAttribute(points: readonly VectorWidthPoint[], precision = 4): string {\n\treturn points.map((p) => [p.t, p.left, p.right].map((n) => formatNumber(n, precision)).join(',')).join(';');\n}\n\n/** Reads `widthProfileToAttribute` output (invalid points are dropped). */\nexport function widthProfileFromAttribute(value: string | undefined): VectorWidthPoint[] {\n\tif (!value) return [];\n\treturn sanitizeWidthProfile(\n\t\tvalue.split(';').map((part) => {\n\t\t\tconst [t, left, right] = part.split(',').map((n) => parseFloat(n));\n\t\t\treturn { t, left, right: Number.isFinite(right) ? right : left };\n\t\t})\n\t);\n}\n\nconst round2 = (n: number) => Math.round(n * 100) / 100;\n\nfunction gradientMarkup(def: SvgGradientDef, extra = ''): string {\n\tconst tag = def.type === 'radial' ? 'radialGradient' : 'linearGradient';\n\tconst attrs = Object.entries(def.attrs)\n\t\t.map(([k, v]) => ` ${k}=\"${escapeAttr(String(v))}\"`)\n\t\t.join('');\n\tconst stops = def.stops\n\t\t.map((s) => `<stop offset=\"${s.offset}\" stop-color=\"${escapeAttr(s.stopColor)}\"${s.stopOpacity !== undefined ? ` stop-opacity=\"${s.stopOpacity}\"` : ''}/>`)\n\t\t.join('');\n\treturn `<${tag} id=\"${escapeAttr(def.id)}\"${attrs}${extra}>${stops}</${tag}>`;\n}\n\n/**\n * A paint server's markup. The first def of a gradient carries `data-vector-gradient` (the\n * gradient as JSON, in the node's space) so import restores what SVG cannot say: midpoints,\n * interpolation, smoothness, conic / reflected / diamond styles and stroke modes.\n */\nfunction paintDefMarkup(def: VectorPaintDef, gradient?: VectorGradient): string {\n\tconst extra = gradient ? ` data-vector-gradient=\"${escapeAttr(JSON.stringify(gradient))}\"` : '';\n\tif (def.type !== 'pattern') return gradientMarkup(def, extra);\n\tconst attrs = Object.entries(def.attrs)\n\t\t.map(([k, v]) => ` ${k}=\"${escapeAttr(String(v))}\"`)\n\t\t.join('');\n\tconst inner = def.gradients.map((g) => gradientMarkup(g)).join('') + def.tiles.map((t) => `<path d=\"${t.d}\" fill=\"${escapeAttr(t.fill)}\"/>`).join('');\n\treturn `<pattern id=\"${escapeAttr(def.id)}\"${attrs}${extra}>${inner}</pattern>`;\n}\n\n/** Markup of a paint's defs (the gradient's data attribute on the first). */\nconst paintMarkup = (paint: VectorPaint | undefined, defs: readonly VectorPaintDef[]) => defs.map((d, i) => paintDefMarkup(d, i === 0 && isVectorGradient(paint) ? paint : undefined));\n\n/**\n * `data-vector-blend` (a blend group's options as JSON, its spine in the group's space) back to\n * a blend, the spine carried by `m` (import bakes group transforms into the children).\n */\nfunction blendFromAttribute(value: string | undefined, m: AffineMatrix): VectorBlend | null {\n\tif (!value) return null;\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(value);\n\t} catch {\n\t\treturn null;\n\t}\n\tif (!raw || typeof raw !== 'object') return null;\n\tconst r = raw as Record<string, unknown>;\n\tconst out: VectorBlend = {};\n\tif (r.spacing === 'steps' || r.spacing === 'distance' || r.spacing === 'smoothColor') out.spacing = r.spacing;\n\tif (typeof r.steps === 'number' && Number.isFinite(r.steps)) out.steps = Math.min(1000, Math.max(1, Math.round(r.steps)));\n\tif (typeof r.distance === 'number' && r.distance > 0) out.distance = r.distance;\n\tif (typeof r.orientToSpine === 'boolean') out.orientToSpine = r.orientToSpine;\n\tif (typeof r.linear === 'boolean') out.linear = r.linear;\n\tif (r.starts && typeof r.starts === 'object') {\n\t\tconst starts = Object.fromEntries(Object.entries(r.starts as Record<string, unknown>).filter((e): e is [string, number] => Number.isInteger(e[1]) && (e[1] as number) >= 0));\n\t\tif (Object.keys(starts).length) out.starts = starts;\n\t}\n\tconst spine = r.spine as VectorSubpath | null | undefined;\n\tif (spine && Array.isArray(spine.anchors) && spine.anchors.every((a) => a && Number.isFinite(a.x) && Number.isFinite(a.y))) {\n\t\tconst sp: VectorSubpath = { closed: !!spine.closed, anchors: spine.anchors.map((a) => ({ x: a.x, y: a.y, ...(a.in ? { in: { x: a.in.x, y: a.in.y } } : {}), ...(a.out ? { out: { x: a.out.x, y: a.out.y } } : {}) })) };\n\t\tout.spine = m === IDENTITY_MATRIX ? sp : transformVectorNode<VectorPathNode>({ id: '', type: 'path', subpaths: [sp] }, m).subpaths[0];\n\t}\n\treturn out;\n}\n\n/** Serializes a vector document as a standalone SVG string. */\nexport function vectorDocumentToSvg(doc: VectorDocument, options: VectorSvgExportOptions = {}): string {\n\tconst precision = options.precision ?? 3;\n\tconst prefix = options.idPrefix ?? 'vg-';\n\tconst pretty = options.pretty !== false;\n\tconst defs: string[] = [];\n\tconst lines: string[] = [];\n\tconst num = (n: number) => formatNumber(n, precision);\n\tconst indent = (depth: number) => (pretty ? ' '.repeat(depth) : '');\n\n\tconst common = (node: VectorNode) => {\n\t\tlet out = ` id=\"${escapeAttr(node.id)}\"`;\n\t\tif (node.transform) out += ` transform=\"matrix(${node.transform.map((n) => formatNumber(n, 6)).join(' ')})\"`;\n\t\tif (node.opacity !== undefined && node.opacity < 1) out += ` opacity=\"${num(node.opacity)}\"`;\n\t\tif (node.visible === false) out += ` display=\"none\"`;\n\t\tif (node.blendStep) out += ` data-vector-blend-step=\"\"`;\n\t\tif (node.type === 'group' && node.blend) out += ` data-vector-blend=\"${escapeAttr(JSON.stringify(node.blend))}\"`;\n\t\tif (node.booleanOperation) out += ` data-vector-boolean-operation=\"${node.booleanOperation}\"`;\n\t\tconst blend = vectorBlendModeCss(node.blendMode);\n\t\tif (blend) out += ` style=\"mix-blend-mode:${blend}\"`;\n\t\tif (node.filters?.length) {\n\t\t\t// The filter as drawn, carrying the filter stack (JSON) for import.\n\t\t\tconst filter = vectorNodeFilterSvg(node, `${prefix}filter-${node.id}`);\n\t\t\tconst data = ` data-vector-filters=\"${escapeAttr(JSON.stringify(node.filters))}\"`;\n\t\t\tif (filter) {\n\t\t\t\tdefs.push(vectorFilterMarkup(filter, data));\n\t\t\t\tout += ` filter=\"url(#${escapeAttr(filter.id)})\"`;\n\t\t\t} else out += data;\n\t\t}\n\t\treturn out;\n\t};\n\n\t/** Opacity and blend mode of one paint layer, as attributes. */\n\tconst layerAttrs = (layer: { opacity?: number; blendMode?: VectorPaintLayer['blendMode'] }) => {\n\t\tlet out = '';\n\t\tif (layer.opacity !== undefined && layer.opacity < 1) out += ` opacity=\"${num(layer.opacity)}\"`;\n\t\tconst blend = vectorBlendModeCss(layer.blendMode);\n\t\tif (blend) out += ` style=\"mix-blend-mode:${blend}\"`;\n\t\treturn out;\n\t};\n\n\t/**\n\t * The elements of a stroke drawn in parts (aligned, arrowed or profiled): the stroke (or its\n\t * outline) clipped or masked by the shape, then the heads. `d` is the shape's path data.\n\t */\n\tconst strokePartLines = (node: VectorPathNode, parts: VectorRenderStrokeParts, strokeValue: string, d: string, lineAttrs: (width: number) => string, depth: number): string[] => {\n\t\tconst out: string[] = [];\n\t\tif (options.strokes === 'outline') {\n\t\t\tconst area = bezierPathToSvg(vectorStrokeOutlinePath(subpathsToBezier(node.subpaths), node), { precision });\n\t\t\tout.push(`${indent(depth)}<path data-vector-part=\"stroke-outline\" d=\"${area}\" fill=\"${escapeAttr(strokeValue)}\"/>`);\n\t\t\treturn out;\n\t\t}\n\t\tlet clip = '';\n\t\tif (parts.clip) {\n\t\t\tconst rule = node.fillRule === 'evenodd' ? 'evenodd' : 'nonzero';\n\t\t\tconst id = escapeAttr(parts.clip.id);\n\t\t\tif (parts.clip.kind === 'clip') {\n\t\t\t\tdefs.push(`<clipPath id=\"${id}\"><path d=\"${d}\" clip-rule=\"${rule}\"/></clipPath>`);\n\t\t\t\tclip = ` clip-path=\"url(#${id})\"`;\n\t\t\t} else {\n\t\t\t\tconst b = parts.clip.bounds;\n\t\t\t\tconst box = `x=\"${num(b.x)}\" y=\"${num(b.y)}\" width=\"${num(b.width)}\" height=\"${num(b.height)}\"`;\n\t\t\t\tdefs.push(`<mask id=\"${id}\" maskUnits=\"userSpaceOnUse\" ${box}><rect ${box} fill=\"#fff\"/><path d=\"${d}\" fill=\"#000\" fill-rule=\"${rule}\"/></mask>`);\n\t\t\t\tclip = ` mask=\"url(#${id})\"`;\n\t\t\t}\n\t\t}\n\t\tif (parts.outline !== undefined) out.push(`${indent(depth)}<path data-vector-part=\"stroke-outline\" d=\"${parts.outline}\" fill=\"${escapeAttr(strokeValue)}\"${clip}/>`);\n\t\telse out.push(`${indent(depth)}<path data-vector-part=\"stroke\" d=\"${parts.d}\" fill=\"none\"${lineAttrs(parts.width)}${clip}/>`);\n\t\tfor (const h of parts.heads) out.push(`${indent(depth)}<path data-vector-part=\"head\" d=\"${h}\" fill=\"${escapeAttr(strokeValue)}\"/>`);\n\t\treturn out;\n\t};\n\n\t/** SVG line attributes of a (layer) node's stroke. */\n\tconst lineAttrsOf = (node: VectorPathNode, strokeValue: string) => (width: number) => {\n\t\tlet out = ` stroke=\"${escapeAttr(strokeValue)}\" stroke-width=\"${num(width)}\"`;\n\t\tif (node.strokeLinecap && node.strokeLinecap !== 'butt') out += ` stroke-linecap=\"${node.strokeLinecap}\"`;\n\t\tif (node.strokeLinejoin && node.strokeLinejoin !== 'miter') out += ` stroke-linejoin=\"${node.strokeLinejoin}\"`;\n\t\tif (node.strokeDasharray?.length) out += ` stroke-dasharray=\"${node.strokeDasharray.map(num).join(' ')}\"`;\n\t\treturn out;\n\t};\n\n\t/** `data-vector-d` / `data-vector-corners` of a path with live corners ('' without). */\n\tconst liveCorners = (node: VectorPathNode) => {\n\t\tconst corners = vectorCornersAttribute(node.subpaths, precision);\n\t\treturn corners ? ` data-vector-d=\"${bezierPathToSvg(subpathsToBezier(node.subpaths, { corners: false }), { precision })}\" data-vector-corners=\"${corners}\"` : '';\n\t};\n\n\t/**\n\t * A path painted through its stacks: a `<g data-vector-path>` carrying the stacks (JSON, for\n\t * import), holding each drawn layer bottom to top. The first part is a fill part with the\n\t * geometry (unpainted when no fill is drawn).\n\t */\n\tconst emitStacked = (node: VectorPathNode, depth: number) => {\n\t\tconst d = vectorPathData(node, precision);\n\t\tconst rule = node.fillRule === 'evenodd' ? ` fill-rule=\"evenodd\"` : '';\n\t\tlet data = ` data-vector-path=\"\" data-vector-fills=\"${escapeAttr(JSON.stringify(vectorFills(node)))}\" data-vector-strokes=\"${escapeAttr(JSON.stringify(vectorStrokes(node)))}\"`;\n\t\tif (node.fillRule === 'evenodd') data += ` data-vector-fill-rule=\"evenodd\"`;\n\t\tlines.push(`${indent(depth)}<g${common(node)}${data}>`);\n\t\tconst layers = vectorPaintLayerNodes(node).filter((l) => l.drawn);\n\t\tlet geometry = liveCorners(node);\n\t\tif (!layers.some((l) => l.kind === 'fill')) {\n\t\t\tlines.push(`${indent(depth + 1)}<path data-vector-part=\"fill\" d=\"${d}\" fill=\"none\"${rule}${geometry}/>`);\n\t\t\tgeometry = '';\n\t\t}\n\t\tfor (const l of layers) {\n\t\t\tconst id = `${prefix}${l.kind}-${node.id}-${l.index}`;\n\t\t\tconst paint = vectorPaintToSvg(l.layer.paint, id, { node: l.node, precision, ...(l.kind === 'stroke' ? { role: 'stroke' as const } : {}) });\n\t\t\tdefs.push(...paintMarkup(l.layer.paint, paint.defs));\n\t\t\tconst attrs = ` data-vector-layer=\"${l.index}\"${layerAttrs(l.layer)}`;\n\t\t\tif (l.kind === 'fill') {\n\t\t\t\tlines.push(`${indent(depth + 1)}<path data-vector-part=\"fill\"${attrs} d=\"${d}\" fill=\"${escapeAttr(paint.value)}\"${rule}${geometry}/>`);\n\t\t\t\tgeometry = '';\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst lineAttrs = lineAttrsOf(l.node, paint.value);\n\t\t\tconst parts = vectorStrokeSvgParts(l.node, `${prefix}clip-${node.id}-${l.index}`, precision);\n\t\t\tif (!parts) {\n\t\t\t\tlines.push(`${indent(depth + 1)}<path data-vector-part=\"stroke\"${attrs} d=\"${d}\" fill=\"none\"${lineAttrs(l.node.strokeWidth ?? 1)}/>`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlines.push(`${indent(depth + 1)}<g data-vector-part=\"stroke-layer\"${attrs}>`);\n\t\t\tlines.push(...strokePartLines(l.node, parts, paint.value, d, lineAttrs, depth + 2));\n\t\t\tlines.push(`${indent(depth + 1)}</g>`);\n\t\t}\n\t\tlines.push(`${indent(depth)}</g>`);\n\t};\n\n\tconst emit = (node: VectorNode, depth: number) => {\n\t\tif (node.visible === false && !options.includeHidden) return;\n\t\tconst wrappers = options.wrap?.[node.id] ?? [];\n\t\twrappers.forEach((w, i) => {\n\t\t\tlet attrs = w.id !== undefined ? ` id=\"${escapeAttr(w.id)}\"` : '';\n\t\t\tif (w.transform) attrs += ` transform=\"${escapeAttr(w.transform)}\"`;\n\t\t\tif (w.clip) {\n\t\t\t\tconst shape = Object.entries(w.clip.attrs).map(([k, v]) => ` ${k}=\"${formatNumber(v, 4)}\"`).join('');\n\t\t\t\tdefs.push(`<clipPath id=\"${escapeAttr(w.clip.id)}\" clipPathUnits=\"objectBoundingBox\"><${w.clip.shape}${w.clip.shapeId !== undefined ? ` id=\"${escapeAttr(w.clip.shapeId)}\"` : ''}${shape}/></clipPath>`);\n\t\t\t\tattrs += ` clip-path=\"url(#${escapeAttr(w.clip.id)})\"`;\n\t\t\t}\n\t\t\tlines.push(`${indent(depth + i)}<g${attrs}>`);\n\t\t});\n\t\temitNode(node, depth + wrappers.length);\n\t\tfor (let i = wrappers.length - 1; i >= 0; i--) lines.push(`${indent(depth + i)}</g>`);\n\t};\n\n\t/** The lines `fn` emits, taken out of the document (for markup that goes into `defs`). */\n\tconst capture = (fn: () => void): string => {\n\t\tconst at = lines.length;\n\t\tfn();\n\t\treturn lines.splice(at).map((l) => l.trim()).join('');\n\t};\n\n\t/**\n\t * A mask group: its appearance on the outer `<g data-vector-mask>`, the contents in an inner\n\t * `<g data-vector-part=\"masked\">` that references the `<clipPath>` or `<mask>`. Effects of the\n\t * group act on the contents, exported as a live group `<id>~content` around them.\n\t */\n\tconst emitMaskGroup = (node: VectorGroupNode & { mask: NonNullable<VectorGroupNode['mask']> }, depth: number) => {\n\t\tconst [source, ...contents] = node.children;\n\t\tconst body: VectorNode[] = node.effects?.length && contents.length ? [{ id: `${node.id}~content`, type: 'group', effects: node.effects, children: contents }] : contents;\n\t\tconst settings = { type: node.mask.type, ...(node.mask.invert ? { invert: true } : {}) };\n\t\tlines.push(`${indent(depth)}<g${common(node)} data-vector-mask=\"${escapeAttr(JSON.stringify(settings))}\">`);\n\t\tconst mask = vectorMaskRenderData(node, `${prefix}mask-${node.id}`, `${prefix}mask-filter-${node.id}`, precision);\n\t\tlet ref = '';\n\t\tif (mask && mask.type !== 'clip') {\n\t\t\tconst b = mask.bounds!;\n\t\t\tconst box = `x=\"${num(b.x)}\" y=\"${num(b.y)}\" width=\"${num(b.width)}\" height=\"${num(b.height)}\"`;\n\t\t\tif (mask.invertFilter) defs.push(vectorFilterMarkup(mask.invertFilter));\n\t\t\tconst inner = capture(() => emitNode(source, depth + 1));\n\t\t\tconst drawn = mask.filter ? `<g data-vector-part=\"mask-invert\" filter=\"${escapeAttr(mask.filter)}\">${inner}</g>` : inner;\n\t\t\tdefs.push(`<mask id=\"${escapeAttr(mask.id)}\" data-vector-part=\"mask-source\" maskUnits=\"userSpaceOnUse\" ${box}${mask.type === 'alpha' ? ' mask-type=\"alpha\"' : ''}>${drawn}</mask>`);\n\t\t\tref = ` mask=\"url(#${escapeAttr(mask.id)})\"`;\n\t\t} else {\n\t\t\t// The mask itself, hidden, for import (a clip's paint is not drawn; a mask turned off is hidden).\n\t\t\tlines.push(`${indent(depth + 1)}<g data-vector-part=\"mask-source\" display=\"none\">`);\n\t\t\temitNode(source, depth + 2);\n\t\t\tlines.push(`${indent(depth + 1)}</g>`);\n\t\t\tif (mask?.clip) {\n\t\t\t\tdefs.push(`<clipPath id=\"${escapeAttr(mask.id)}\">${mask.clip.map((c) => `<path d=\"${c.d}\" clip-rule=\"${c.clipRule}\"/>`).join('')}</clipPath>`);\n\t\t\t\tref = ` clip-path=\"url(#${escapeAttr(mask.id)})\"`;\n\t\t\t}\n\t\t}\n\t\tlines.push(`${indent(depth + 1)}<g data-vector-part=\"masked\"${ref}>`);\n\t\tfor (const child of body) emit(child, depth + 2);\n\t\tlines.push(`${indent(depth + 1)}</g>`);\n\t\tlines.push(`${indent(depth)}</g>`);\n\t};\n\n\tconst emitNode = (node: VectorNode, depth: number) => {\n\t\tif (isVectorMaskGroup(node)) {\n\t\t\temitMaskGroup(node, depth);\n\t\t\treturn;\n\t\t}\n\t\tif (hasOwnVectorAppearance(node)) {\n\t\t\t// Live: what it draws, then the editable source (hidden) for import.\n\t\t\tconst live = {\n\t\t\t\tkind: node.type,\n\t\t\t\t...(node.effects?.length ? { effects: node.effects } : {}),\n\t\t\t\t...(node.type === 'group' && node.liveBoolean ? { liveBoolean: node.liveBoolean } : {})\n\t\t\t};\n\t\t\tlines.push(`${indent(depth)}<g${common(node)} data-vector-live=\"${escapeAttr(JSON.stringify(live))}\">`);\n\t\t\tlines.push(`${indent(depth + 1)}<g data-vector-part=\"appearance\">`);\n\t\t\tfor (const shape of vectorAppearanceNodes(node)) emit(shape, depth + 2);\n\t\t\tlines.push(`${indent(depth + 1)}</g>`);\n\t\t\tlines.push(`${indent(depth + 1)}<g data-vector-part=\"source\" display=\"none\">`);\n\t\t\tif (node.type === 'path') {\n\t\t\t\tconst { effects: _e, transform: _t, opacity: _o, visible: _v, booleanOperation: _b, blendStep: _s, blendMode: _bm, filters: _f, ...source } = node;\n\t\t\t\temit({ ...source, id: `${node.id}~src` }, depth + 2);\n\t\t\t} else for (const child of node.children) emit(child, depth + 2);\n\t\t\tlines.push(`${indent(depth + 1)}</g>`);\n\t\t\tlines.push(`${indent(depth)}</g>`);\n\t\t\treturn;\n\t\t}\n\t\tif (node.type === 'group') {\n\t\t\tlines.push(`${indent(depth)}<g${common(node)}>`);\n\t\t\tfor (const child of node.children) emit(child, depth + 1);\n\t\t\tlines.push(`${indent(depth)}</g>`);\n\t\t\treturn;\n\t\t}\n\t\tif (needsVectorPaintLayers(node)) {\n\t\t\temitStacked(node, depth);\n\t\t\treturn;\n\t\t}\n\t\tconst fill = vectorPaintToSvg(node.fill, `${prefix}fill-${node.id}`, { node, precision });\n\t\tconst stroke = vectorPaintToSvg(node.stroke, `${prefix}stroke-${node.id}`, { node, role: 'stroke', precision });\n\t\tdefs.push(...paintMarkup(node.fill, fill.defs), ...paintMarkup(node.stroke, stroke.defs));\n\t\tconst d = vectorPathData(node, precision);\n\t\tconst live = liveCorners(node);\n\t\tconst fillAttrs = ` fill=\"${escapeAttr(fill.value)}\"${node.fillRule === 'evenodd' ? ` fill-rule=\"evenodd\"` : ''}`;\n\t\tconst lineAttrs = lineAttrsOf(node, stroke.value);\n\t\tconst parts = stroke.value !== 'none' ? vectorStrokeSvgParts(node, `${prefix}clip-${node.id}`, precision) : undefined;\n\t\tif (!parts) {\n\t\t\tlines.push(`${indent(depth)}<path${common(node)} d=\"${d}\"${fillAttrs}${stroke.value !== 'none' ? lineAttrs(node.strokeWidth ?? 1) : ''}${live}/>`);\n\t\t\treturn;\n\t\t}\n\t\t// The stroke options SVG has no attribute for, kept for import.\n\t\tlet data = ` data-vector-path=\"\" data-vector-stroke-width=\"${num(node.strokeWidth ?? 1)}\"`;\n\t\tif (parts.align !== 'center') data += ` data-vector-stroke-align=\"${parts.align}\"`;\n\t\tif (node.startArrow) data += ` data-vector-start-arrow=\"${node.startArrow}\"`;\n\t\tif (node.endArrow) data += ` data-vector-end-arrow=\"${node.endArrow}\"`;\n\t\tif (node.arrowAlign && node.arrowAlign !== 'extend') data += ` data-vector-arrow-align=\"${node.arrowAlign}\"`;\n\t\tif (node.startArrowScale !== undefined && node.startArrowScale !== 1) data += ` data-vector-start-arrow-scale=\"${num(node.startArrowScale)}\"`;\n\t\tif (node.endArrowScale !== undefined && node.endArrowScale !== 1) data += ` data-vector-end-arrow-scale=\"${num(node.endArrowScale)}\"`;\n\t\tconst profile = strokeWidthProfileOf(node);\n\t\tif (profile) {\n\t\t\t// Drawn as a filled outline: keep the line style the outline was built with.\n\t\t\tdata += ` data-vector-width-profile=\"${widthProfileToAttribute(profile)}\"`;\n\t\t\tif (node.strokeLinecap && node.strokeLinecap !== 'butt') data += ` data-vector-stroke-linecap=\"${node.strokeLinecap}\"`;\n\t\t\tif (node.strokeLinejoin && node.strokeLinejoin !== 'miter') data += ` data-vector-stroke-linejoin=\"${node.strokeLinejoin}\"`;\n\t\t\tif (node.strokeDasharray?.length) data += ` data-vector-stroke-dasharray=\"${node.strokeDasharray.map(num).join(' ')}\"`;\n\t\t}\n\t\tlines.push(`${indent(depth)}<g${common(node)}${data}>`);\n\t\tlines.push(`${indent(depth + 1)}<path data-vector-part=\"fill\" d=\"${d}\"${fillAttrs}${live}/>`);\n\t\tlines.push(...strokePartLines(node, parts, stroke.value, d, lineAttrs, depth + 1));\n\t\tlines.push(`${indent(depth)}</g>`);\n\t};\n\n\tfor (const node of doc.nodes) emit(node, 1);\n\tconst w = num(doc.width);\n\tconst h = num(doc.height);\n\tconst nl = pretty ? '\\n' : '';\n\tconst out = [`<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${h}\" viewBox=\"0 0 ${w} ${h}\">`];\n\tif (defs.length) out.push(`${indent(1)}<defs>${defs.join('')}</defs>`);\n\tif (doc.background && options.background !== false && hasPaint(doc.background)) {\n\t\tout.push(`${indent(1)}<rect width=\"${w}\" height=\"${h}\" fill=\"${escapeAttr(doc.background)}\"/>`);\n\t}\n\tout.push(...lines, '</svg>');\n\treturn out.join(nl);\n}\n\n// ---------------------------------------------------------------------------\n// Import\n// ---------------------------------------------------------------------------\n\nconst BOOLEAN_OPERATIONS: readonly VectorBooleanOperation[] = ['union', 'subtract', 'intersect', 'exclude'];\n\nexport interface VectorSvgImportOptions {\n\t/** Makes ids for elements without a usable `id` (default `node-1`, `node-2`, …). */\n\tcreateId?: () => string;\n\t/**\n\t * Merge neighbouring shapes with the same outline into one path with stacked fills and strokes,\n\t * as design tools export several paints on one layer (default false: an SVG may hold identical\n\t * shapes on purpose). Shapes merge only when the paint order stays the same (no stroke under a\n\t * later fill) and they share opacity, visibility, filters and blend mode.\n\t */\n\tmergeStackedShapes?: boolean;\n}\n\ninterface Element {\n\tname: string;\n\tattrs: Record<string, string>;\n\tchildren: Element[];\n}\n\n/** A tiny non-validating XML reader: elements and attributes only. */\nfunction parseXml(source: string): Element | null {\n\tconst text = source\n\t\t.replace(/<!--[\\s\\S]*?-->/g, '')\n\t\t.replace(/<\\?[\\s\\S]*?\\?>/g, '')\n\t\t.replace(/<!\\[CDATA\\[[\\s\\S]*?\\]\\]>/g, '')\n\t\t.replace(/<!DOCTYPE[^>[]*(\\[[\\s\\S]*?\\])?\\s*>/gi, '');\n\tconst root: Element = { name: '#root', attrs: {}, children: [] };\n\tconst stack: Element[] = [root];\n\tconst tag = /<(\\/?)([A-Za-z_][\\w:.-]*)((?:\\s+[^\\s=/>]+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>]+))?)*)\\s*(\\/?)>/g;\n\tconst attr = /([^\\s=/>]+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+)))?/g;\n\tlet m: RegExpExecArray | null;\n\twhile ((m = tag.exec(text))) {\n\t\tconst [, closing, rawName, rawAttrs, selfClosing] = m;\n\t\tconst name = rawName.replace(/^svg:/, '');\n\t\tif (closing) {\n\t\t\tfor (let i = stack.length - 1; i > 0; i--) {\n\t\t\t\tif (stack[i].name === name) {\n\t\t\t\t\tstack.length = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tconst attrs: Record<string, string> = {};\n\t\tlet a: RegExpExecArray | null;\n\t\tattr.lastIndex = 0;\n\t\twhile ((a = attr.exec(rawAttrs))) attrs[a[1]] = decodeEntities(a[2] ?? a[3] ?? a[4] ?? '');\n\t\tconst el: Element = { name, attrs, children: [] };\n\t\tstack[stack.length - 1].children.push(el);\n\t\tif (!selfClosing) stack.push(el);\n\t}\n\tconst find = (el: Element): Element | null => {\n\t\tif (el.name === 'svg') return el;\n\t\tfor (const c of el.children) {\n\t\t\tconst f = find(c);\n\t\t\tif (f) return f;\n\t\t}\n\t\treturn null;\n\t};\n\treturn find(root);\n}\n\nfunction decodeEntities(s: string): string {\n\treturn s\n\t\t.replace(/&#x([0-9a-f]+);/gi, (_, h) => String.fromCodePoint(parseInt(h, 16)))\n\t\t.replace(/&#(\\d+);/g, (_, d) => String.fromCodePoint(parseInt(d, 10)))\n\t\t.replace(/"/g, '\"')\n\t\t.replace(/'/g, \"'\")\n\t\t.replace(/</g, '<')\n\t\t.replace(/>/g, '>')\n\t\t.replace(/&/g, '&');\n}\n\nconst numberList = (s: string | undefined): number[] =>\n\t(s ?? '')\n\t\t.trim()\n\t\t.split(/[\\s,]+|(?=-)(?<![eE])/)\n\t\t.filter(Boolean)\n\t\t.map(Number)\n\t\t.filter((n) => Number.isFinite(n));\n\nconst length = (s: string | undefined, fallback = 0): number => {\n\tif (s === undefined) return fallback;\n\tconst n = parseFloat(s);\n\treturn Number.isFinite(n) ? n : fallback;\n};\n\n/** Parses an SVG `transform` attribute into one matrix. */\nexport function parseSvgTransform(value: string | undefined): AffineMatrix {\n\tlet m: AffineMatrix = IDENTITY_MATRIX;\n\tif (!value) return m;\n\tconst re = /(matrix|translate|scale|rotate|skewX|skewY)\\s*\\(([^)]*)\\)/g;\n\tlet t: RegExpExecArray | null;\n\twhile ((t = re.exec(value))) {\n\t\tconst n = numberList(t[2]);\n\t\tlet step: AffineMatrix = IDENTITY_MATRIX;\n\t\tswitch (t[1]) {\n\t\t\tcase 'matrix':\n\t\t\t\tif (n.length >= 6) step = [n[0], n[1], n[2], n[3], n[4], n[5]];\n\t\t\t\tbreak;\n\t\t\tcase 'translate':\n\t\t\t\tstep = translateMatrix(n[0] ?? 0, n[1] ?? 0);\n\t\t\t\tbreak;\n\t\t\tcase 'scale':\n\t\t\t\tstep = scaleMatrix(n[0] ?? 1, n[1] ?? n[0] ?? 1);\n\t\t\t\tbreak;\n\t\t\tcase 'rotate':\n\t\t\t\tstep = rotateMatrix(n[0] ?? 0, { x: n[1] ?? 0, y: n[2] ?? 0 });\n\t\t\t\tbreak;\n\t\t\tcase 'skewX':\n\t\t\t\tstep = [1, 0, Math.tan(((n[0] ?? 0) * Math.PI) / 180), 1, 0, 0];\n\t\t\t\tbreak;\n\t\t\tcase 'skewY':\n\t\t\t\tstep = [1, Math.tan(((n[0] ?? 0) * Math.PI) / 180), 0, 1, 0, 0];\n\t\t\t\tbreak;\n\t\t}\n\t\t// SVG lists apply right to left: `translate(…) rotate(…)` rotates first.\n\t\tm = multiplyMatrices(step, m);\n\t}\n\treturn m;\n}\n\ninterface InheritedStyle {\n\tfill: string;\n\tstroke: string;\n\tstrokeWidth: string;\n\tstrokeLinecap?: string;\n\tstrokeLinejoin?: string;\n\tstrokeDasharray?: string;\n\tfillRule?: string;\n\tfillOpacity?: string;\n\tstrokeOpacity?: string;\n\t/** Not inherited: the element's own `mix-blend-mode` (reset for each element). */\n\tmixBlendMode?: string;\n\t/** Not inherited: the element's own `filter` (reset for each element). */\n\tfilter?: string;\n}\n\nconst STYLE_KEYS: Record<string, keyof InheritedStyle> = {\n\tfill: 'fill',\n\tstroke: 'stroke',\n\t'stroke-width': 'strokeWidth',\n\t'stroke-linecap': 'strokeLinecap',\n\t'stroke-linejoin': 'strokeLinejoin',\n\t'stroke-dasharray': 'strokeDasharray',\n\t'fill-rule': 'fillRule',\n\t'fill-opacity': 'fillOpacity',\n\t'stroke-opacity': 'strokeOpacity',\n\t'mix-blend-mode': 'mixBlendMode',\n\tfilter: 'filter'\n};\n\nfunction readStyle(el: Element, parent: InheritedStyle): InheritedStyle {\n\tconst { mixBlendMode: _m, filter: _f, ...inherited } = parent;\n\tconst out: InheritedStyle = { ...inherited };\n\tfor (const [attr, key] of Object.entries(STYLE_KEYS)) if (el.attrs[attr] !== undefined) out[key] = el.attrs[attr].trim();\n\tfor (const decl of (el.attrs.style ?? '').split(';')) {\n\t\tconst i = decl.indexOf(':');\n\t\tif (i < 0) continue;\n\t\tconst key = STYLE_KEYS[decl.slice(0, i).trim()];\n\t\tif (key) out[key] = decl.slice(i + 1).replace(/!important/, '').trim();\n\t}\n\treturn out;\n}\n\nfunction readStops(el: Element): VectorGradientStop[] {\n\treturn el.children\n\t\t.filter((c) => c.name === 'stop')\n\t\t.map((c) => {\n\t\t\tconst style = Object.fromEntries(\n\t\t\t\t(c.attrs.style ?? '')\n\t\t\t\t\t.split(';')\n\t\t\t\t\t.map((d) => d.split(':').map((s) => s.trim()))\n\t\t\t\t\t.filter((d) => d.length === 2)\n\t\t\t);\n\t\t\tconst raw = c.attrs.offset ?? '0';\n\t\t\tconst offset = raw.endsWith('%') ? parseFloat(raw) / 100 : parseFloat(raw);\n\t\t\tconst opacity = style['stop-opacity'] ?? c.attrs['stop-opacity'];\n\t\t\treturn {\n\t\t\t\toffset: Number.isFinite(offset) ? Math.max(0, Math.min(1, offset)) : 0,\n\t\t\t\tcolor: style['stop-color'] ?? c.attrs['stop-color'] ?? '#000',\n\t\t\t\t...(opacity !== undefined ? { opacity: parseFloat(opacity) } : {})\n\t\t\t};\n\t\t});\n}\n\nconst fraction = (s: string | undefined, fallback: number) => {\n\tif (s === undefined) return fallback;\n\tconst n = parseFloat(s);\n\tif (!Number.isFinite(n)) return fallback;\n\treturn s.trim().endsWith('%') ? n / 100 : n;\n};\n\nconst PAINT_SERVERS = new Set(['linearGradient', 'radialGradient', 'pattern']);\n\nfunction collectGradients(el: Element, raw: Map<string, Element>) {\n\tif (PAINT_SERVERS.has(el.name) && el.attrs.id) raw.set(el.attrs.id, el);\n\tfor (const c of el.children) collectGradients(c, raw);\n}\n\n/** Every `<clipPath>` and `<mask>` with an id. */\nfunction collectMasks(el: Element, raw: Map<string, Element>) {\n\tif ((el.name === 'clipPath' || el.name === 'mask') && el.attrs.id) raw.set(el.attrs.id, el);\n\tfor (const c of el.children) collectMasks(c, raw);\n}\n\n/** An attribute, or the same property in the element's `style`. */\nfunction attrOrStyle(el: Element, name: string): string | undefined {\n\tfor (const decl of (el.attrs.style ?? '').split(';')) {\n\t\tconst i = decl.indexOf(':');\n\t\tif (i >= 0 && decl.slice(0, i).trim() === name) return decl.slice(i + 1).replace(/!important/, '').trim();\n\t}\n\treturn el.attrs[name];\n}\n\n/** The id an `url(#id)` reference names, or null. */\nfunction urlId(value: string | undefined): string | null {\n\treturn /^url\\(\\s*['\"]?#([^'\")\\s]+)['\"]?\\s*\\)/.exec((value ?? '').trim())?.[1] ?? null;\n}\n\n/** A `<clipPath>`'s content with each `clip-rule` read as the `fill-rule` import knows. */\nfunction clipRulesAsFillRules(el: Element): Element {\n\tconst attrs = { ...el.attrs };\n\tconst rule = attrOrStyle(el, 'clip-rule');\n\tif (rule) attrs['fill-rule'] = rule;\n\treturn { ...el, attrs, children: el.children.map(clipRulesAsFillRules) };\n}\n\nfunction collectFilters(el: Element, raw: Map<string, Element>) {\n\tif (el.name === 'filter' && el.attrs.id) raw.set(el.attrs.id, el);\n\tfor (const c of el.children) collectFilters(c, raw);\n}\n\n/**\n * A `<filter>` as vector filters: our own export's `data-vector-filters`, or the simple filters\n * other tools write — `feDropShadow`, or a lone `feGaussianBlur` of the source (a layer blur).\n * Anything else is skipped (empty list).\n */\nfunction filtersFromElement(el: Element): VectorFilter[] {\n\tconst own = el.attrs['data-vector-filters'];\n\tif (own !== undefined) {\n\t\ttry {\n\t\t\treturn sanitizeVectorFilters(JSON.parse(own));\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\tconst prims = el.children.filter((c) => c.name.startsWith('fe'));\n\tconst out: unknown[] = [];\n\tfor (const p of prims) {\n\t\tconst std = numberList(p.attrs.stdDeviation)[0] ?? 0;\n\t\tif (p.name === 'feDropShadow') {\n\t\t\tconst style = Object.fromEntries(\n\t\t\t\t(p.attrs.style ?? '')\n\t\t\t\t\t.split(';')\n\t\t\t\t\t.map((d) => d.split(':').map((x) => x.trim()))\n\t\t\t\t\t.filter((d) => d.length === 2)\n\t\t\t);\n\t\t\tconst opacity = parseFloat(style['flood-opacity'] ?? p.attrs['flood-opacity'] ?? '1');\n\t\t\tout.push({\n\t\t\t\ttype: 'dropShadow',\n\t\t\t\tparams: {\n\t\t\t\t\tcolor: style['flood-color'] ?? p.attrs['flood-color'] ?? '#000000',\n\t\t\t\t\topacity: Number.isFinite(opacity) ? opacity : 1,\n\t\t\t\t\toffsetX: length(p.attrs.dx, 2),\n\t\t\t\t\toffsetY: length(p.attrs.dy, 2),\n\t\t\t\t\tblur: std * 2,\n\t\t\t\t\tspread: 0\n\t\t\t\t}\n\t\t\t});\n\t\t} else if (p.name === 'feGaussianBlur' && prims.length === 1 && (p.attrs.in === undefined || p.attrs.in === 'SourceGraphic')) {\n\t\t\tout.push({ type: 'layerBlur', params: { radius: std * 2 } });\n\t\t} else return [];\n\t}\n\treturn sanitizeVectorFilters(out);\n}\n\n/** The gradient a `data-vector-gradient` attribute (our own export) holds, or null. */\nfunction gradientFromAttribute(value: string | undefined): VectorGradient | null {\n\tif (!value) return null;\n\ttry {\n\t\treturn sanitizeVectorGradient(JSON.parse(value));\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * A `<linearGradient>` / `<radialGradient>` (attributes and stops inherited through `href`) as a\n * vector gradient: `gradientUnits`, `gradientTransform`, `spreadMethod`, `fx`, `fy` and `fr` read.\n * Box gradients that match the angle / centre shorthand keep it.\n */\nfunction resolveGradient(id: string, raw: Map<string, Element>): VectorGradient | null {\n\tconst first = raw.get(id);\n\tif (!first) return null;\n\tconst own = gradientFromAttribute(first.attrs['data-vector-gradient']);\n\tif (own) return own;\n\tif (first.name === 'pattern') return null;\n\t// The href chain: attributes and stops come from the nearest element that has them.\n\tconst chain: Element[] = [];\n\tfor (let el: Element | undefined = first; el && !chain.includes(el) && chain.length < 32; ) {\n\t\tchain.push(el);\n\t\tconst href: string | undefined = el.attrs.href ?? el.attrs['xlink:href'];\n\t\tel = href?.startsWith('#') ? raw.get(href.slice(1)) : undefined;\n\t}\n\tconst attr = (name: string) => chain.find((el) => el.attrs[name] !== undefined)?.attrs[name];\n\tconst stops = chain.map(readStops).find((s) => s.length) ?? [];\n\tif (!stops.length) return null;\n\tconst user = attr('gradientUnits') === 'userSpaceOnUse';\n\tconst t = parseSvgTransform(attr('gradientTransform'));\n\tconst plain = t === IDENTITY_MATRIX || (t[0] === 1 && t[1] === 0 && t[2] === 0 && t[3] === 1 && t[4] === 0 && t[5] === 0);\n\tconst at = (name: string, fallback: number) => (user ? length(attr(name), fallback) : fraction(attr(name), fallback));\n\tconst spreadAttr = attr('spreadMethod');\n\tconst spread = spreadAttr === 'reflect' || spreadAttr === 'repeat' ? { spread: spreadAttr as 'reflect' | 'repeat' } : {};\n\tconst map = (p: { x: number; y: number }) => applySvgMatrix(t, p);\n\tif (first.name === 'radialGradient') {\n\t\tconst cx = at('cx', user ? 0 : 0.5);\n\t\tconst cy = at('cy', user ? 0 : 0.5);\n\t\tconst r = at('r', user ? 0 : 0.5);\n\t\tconst fx = attr('fx') !== undefined ? at('fx', cx) : cx;\n\t\tconst fy = attr('fy') !== undefined ? at('fy', cy) : cy;\n\t\tconst fr = attr('fr') !== undefined ? at('fr', 0) : 0;\n\t\tconst focal = Math.abs(fx - cx) > 1e-9 || Math.abs(fy - cy) > 1e-9;\n\t\tif (!user && plain && !focal && !(fr > 0)) return { type: 'radial', cx, cy, r, stops, ...spread };\n\t\tconst g: VectorGradient = {\n\t\t\ttype: 'radial',\n\t\t\tstops,\n\t\t\tstart: map({ x: cx, y: cy }),\n\t\t\tend: map({ x: cx + r, y: cy }),\n\t\t\twidth: map({ x: cx, y: cy + r }),\n\t\t\tunits: user ? 'userSpaceOnUse' : 'objectBoundingBox',\n\t\t\t...spread\n\t\t};\n\t\tif (focal) g.focal = map({ x: fx, y: fy });\n\t\tif (fr > 0 && r > 0) g.focalRadius = Math.min(0.99, fr / r);\n\t\treturn g;\n\t}\n\tconst x1 = at('x1', 0);\n\tconst y1 = at('y1', 0);\n\tconst x2 = attr('x2') !== undefined ? at('x2', 1) : user ? 0 : 1;\n\tconst y2 = at('y2', 0);\n\tif (!user && plain) {\n\t\t// CSS angle: 0° points up, clockwise. Keep the shorthand when it paints the same.\n\t\tconst angle = Math.round(((Math.atan2(x2 - x1, -(y2 - y1)) * 180) / Math.PI + 360) % 360);\n\t\tconst c = angleToSvgLinearCoords(angle);\n\t\tconst len = Math.hypot(x2 - x1, y2 - y1);\n\t\tconst d = len > 0 ? { x: (x2 - x1) / len, y: (y2 - y1) / len } : { x: 0, y: 1 };\n\t\tconst along = (x: number, y: number) => x * d.x + y * d.y;\n\t\tconst same =\n\t\t\tlen > 0 &&\n\t\t\tMath.abs((c.x2 - c.x1) * d.y - (c.y2 - c.y1) * d.x) < 1e-3 &&\n\t\t\tMath.abs(along(c.x1, c.y1) - along(x1, y1)) < 1e-3 &&\n\t\t\tMath.abs(along(c.x2, c.y2) - along(x2, y2)) < 1e-3;\n\t\tif (same) return { type: 'linear', angle, stops, ...spread };\n\t}\n\tconst start = { x: x1, y: y1 };\n\tconst end = { x: x2, y: y2 };\n\treturn {\n\t\ttype: 'linear',\n\t\tstops,\n\t\tstart: map(start),\n\t\tend: map(end),\n\t\twidth: map({ x: x1 - (y2 - y1), y: y1 + (x2 - x1) }),\n\t\tunits: user ? 'userSpaceOnUse' : 'objectBoundingBox',\n\t\t...spread\n\t};\n}\n\nconst applySvgMatrix = (m: AffineMatrix, p: { x: number; y: number }) => ({ x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] });\n\n/** Parses SVG markup into a vector document. Throws when there is no `<svg>` element. */\nexport function parseVectorSvg(svg: string, options: VectorSvgImportOptions = {}): VectorDocument {\n\tconst root = parseXml(svg);\n\tif (!root) throw new Error('The markup has no <svg> element.');\n\tlet counter = 0;\n\tconst usedIds = new Set<string>();\n\tconst createId = options.createId ?? (() => `node-${++counter}`);\n\tconst idFor = (el: Element) => {\n\t\tconst id = el.attrs.id;\n\t\tif (id && !usedIds.has(id)) {\n\t\t\tusedIds.add(id);\n\t\t\treturn id;\n\t\t}\n\t\tlet next = createId();\n\t\twhile (usedIds.has(next)) next = createId();\n\t\tusedIds.add(next);\n\t\treturn next;\n\t};\n\n\tconst rawGradients = new Map<string, Element>();\n\tcollectGradients(root, rawGradients);\n\tconst rawFilters = new Map<string, Element>();\n\tcollectFilters(root, rawFilters);\n\tconst rawMasks = new Map<string, Element>();\n\tcollectMasks(root, rawMasks);\n\t/** Masks being read (a mask that refers back to itself is ignored there). */\n\tconst resolving = new Set<string>();\n\tconst filtersOf = (value: string | undefined): VectorFilter[] => {\n\t\tconst url = /^url\\(\\s*['\"]?#([^'\")\\s]+)['\"]?\\s*\\)/.exec((value ?? '').trim());\n\t\tconst el = url ? rawFilters.get(url[1]) : undefined;\n\t\treturn el ? filtersFromElement(el) : [];\n\t};\n\tconst paint = (value: string): VectorPaint => {\n\t\tconst v = value.trim();\n\t\tconst url = /^url\\(\\s*['\"]?#([^'\")\\s]+)['\"]?\\s*\\)/.exec(v);\n\t\tif (url) return resolveGradient(url[1], rawGradients) ?? null;\n\t\tif (v === 'none' || v === '') return null;\n\t\treturn v;\n\t};\n\n\tconst viewBox = numberList(root.attrs.viewBox);\n\tconst width = viewBox.length === 4 ? viewBox[2] : length(root.attrs.width, 300);\n\tconst height = viewBox.length === 4 ? viewBox[3] : length(root.attrs.height, 150);\n\tconst rootMatrix: AffineMatrix = viewBox.length === 4 ? translateMatrix(-viewBox[0], -viewBox[1]) : IDENTITY_MATRIX;\n\n\tconst SKIP = new Set(['defs', 'clipPath', 'mask', 'symbol', 'style', 'title', 'desc', 'metadata', 'pattern', 'marker', 'filter', 'text', 'image', 'use', 'linearGradient', 'radialGradient', 'script', 'foreignObject']);\n\n\tconst shapeOf = (el: Element): VectorSubpath[] | null => {\n\t\tconst a = el.attrs;\n\t\tswitch (el.name) {\n\t\t\tcase 'path': {\n\t\t\t\t// Our own export: the sharp path and its live corners.\n\t\t\t\tif (a['data-vector-d'] && a['data-vector-corners']) {\n\t\t\t\t\tconst sharp = bezierToSubpaths(a['data-vector-d']);\n\t\t\t\t\tif (sharp.length) return applyVectorCornersAttribute(sharp, a['data-vector-corners']);\n\t\t\t\t}\n\t\t\t\treturn a.d ? bezierToSubpaths(a.d) : null;\n\t\t\t}\n\t\t\tcase 'rect': {\n\t\t\t\tconst w = length(a.width);\n\t\t\t\tconst h = length(a.height);\n\t\t\t\tif (w <= 0 || h <= 0) return null;\n\t\t\t\tconst r = length(a.rx ?? a.ry, 0);\n\t\t\t\treturn [rectSubpath({ x: length(a.x), y: length(a.y), width: w, height: h }, r)];\n\t\t\t}\n\t\t\tcase 'circle': {\n\t\t\t\tconst r = length(a.r);\n\t\t\t\tif (r <= 0) return null;\n\t\t\t\tconst cx = length(a.cx);\n\t\t\t\tconst cy = length(a.cy);\n\t\t\t\treturn [ellipseSubpath({ x: cx - r, y: cy - r, width: 2 * r, height: 2 * r })];\n\t\t\t}\n\t\t\tcase 'ellipse': {\n\t\t\t\tconst rx = length(a.rx);\n\t\t\t\tconst ry = length(a.ry);\n\t\t\t\tif (rx <= 0 || ry <= 0) return null;\n\t\t\t\treturn [ellipseSubpath({ x: length(a.cx) - rx, y: length(a.cy) - ry, width: 2 * rx, height: 2 * ry })];\n\t\t\t}\n\t\t\tcase 'line':\n\t\t\t\treturn [lineSubpath({ x: length(a.x1), y: length(a.y1) }, { x: length(a.x2), y: length(a.y2) })];\n\t\t\tcase 'polyline':\n\t\t\tcase 'polygon': {\n\t\t\t\tconst n = numberList(a.points);\n\t\t\t\tif (n.length < 4) return null;\n\t\t\t\tconst anchors = [];\n\t\t\t\tfor (let i = 0; i + 1 < n.length; i += 2) anchors.push({ x: n[i], y: n[i + 1] });\n\t\t\t\treturn [{ anchors, closed: el.name === 'polygon' }];\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t};\n\n\tconst strokeStyle = (s: InheritedStyle): Partial<VectorPathNode> => ({\n\t\t...(s.strokeWidth ? { strokeWidth: length(s.strokeWidth, 1) } : {}),\n\t\t...(s.strokeLinecap ? { strokeLinecap: s.strokeLinecap as VectorLineCap } : {}),\n\t\t...(s.strokeLinejoin ? { strokeLinejoin: s.strokeLinejoin as VectorLineJoin } : {}),\n\t\t...(s.strokeDasharray && s.strokeDasharray !== 'none' ? { strokeDasharray: numberList(s.strokeDasharray) } : {}),\n\t\t...(s.fillRule === 'evenodd' ? { fillRule: 'evenodd' as VectorFillRule } : {})\n\t});\n\n\t/** A node mapped by `m` (scale and skew baked in, stroke width scaled). */\n\tconst placed = (node: VectorPathNode, m: AffineMatrix): VectorPathNode => {\n\t\tif (m === IDENTITY_MATRIX) return node;\n\t\tconst next = transformVectorNode(node, m);\n\t\tconst strokeScale = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));\n\t\tif (strokeScale > 0 && Math.abs(strokeScale - 1) > 1e-9) {\n\t\t\tif (hasPaint(next.stroke)) next.strokeWidth = (next.strokeWidth ?? 1) * strokeScale;\n\t\t\tif (Array.isArray(next.strokes)) next.strokes = next.strokes.map((l) => ((l.kind ?? 'paint') === 'paint' ? { ...l, strokeWidth: (l.strokeWidth ?? 1) * strokeScale } : l));\n\t\t}\n\t\treturn normalizeVectorPaintStacks(next, 'stack');\n\t};\n\n\t/** Paint with an element's `fill-opacity` / `stroke-opacity`: a one-layer stack when it is below 1. */\n\tconst withOpacities = (node: VectorPathNode, s: InheritedStyle): VectorPathNode => {\n\t\tlet out = node;\n\t\tconst fo = s.fillOpacity !== undefined ? fraction(s.fillOpacity, 1) : 1;\n\t\tconst so = s.strokeOpacity !== undefined ? fraction(s.strokeOpacity, 1) : 1;\n\t\tif (fo < 1 && hasPaint(out.fill)) out = withVectorFills(out, [{ paint: out.fill!, opacity: Math.max(0, fo) }]);\n\t\tif (so < 1 && hasPaint(out.stroke)) out = withVectorStrokes(out, vectorStrokes(out).map((l) => ({ ...l, opacity: Math.max(0, so) })));\n\t\treturn out;\n\t};\n\n\t/**\n\t * A `<g data-vector-path data-vector-fills>` from our own export (a path painted through its\n\t * stacks) as the path it came from: the geometry of its first fill part, the stacks from JSON.\n\t */\n\tconst stackedPath = (g: Element, _style: InheritedStyle, m: AffineMatrix, meta: Partial<VectorPathNode>): VectorPathNode | null => {\n\t\tconst fillEl = g.children.find((c) => c.name === 'path' && c.attrs['data-vector-part'] === 'fill');\n\t\tconst subpaths = fillEl ? shapeOf(fillEl) : null;\n\t\tif (!subpaths?.length) return null;\n\t\tconst parse = (v: string | undefined) => {\n\t\t\ttry {\n\t\t\t\treturn v ? JSON.parse(v) : [];\n\t\t\t} catch {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t};\n\t\tlet node: VectorPathNode = { id: idFor(g), type: 'path', ...meta, subpaths, fill: null, stroke: null };\n\t\tif (g.attrs['data-vector-fill-rule'] === 'evenodd') node.fillRule = 'evenodd';\n\t\tnode = withVectorFills(node, sanitizeVectorFills(parse(g.attrs['data-vector-fills'])));\n\t\tnode = withVectorStrokes(node, sanitizeVectorStrokes(parse(g.attrs['data-vector-strokes'])));\n\t\treturn placed(node, m);\n\t};\n\n\t/**\n\t * Neighbouring paths with the same outline merged into one with stacked paints (see\n\t * `mergeStackedShapes`).\n\t */\n\tconst mergeStacked = (nodes: VectorNode[]): VectorNode[] => {\n\t\tconst out: VectorNode[] = [];\n\t\tconst key = (n: VectorPathNode) =>\n\t\t\tJSON.stringify([n.subpaths, n.transform ?? null, n.opacity ?? 1, n.visible !== false, n.filters ?? null, n.blendMode ?? 'normal', n.fillRule ?? 'nonzero', n.booleanOperation ?? null]);\n\t\tfor (const n of nodes) {\n\t\t\tconst prev = out[out.length - 1];\n\t\t\tif (prev?.type === 'path' && n.type === 'path' && key(prev) === key(n)) {\n\t\t\t\tconst fills = vectorFills(n);\n\t\t\t\tconst strokesBefore = vectorStrokes(prev);\n\t\t\t\t// A later fill would land under an earlier stroke: keep them apart.\n\t\t\t\tif (!(fills.length && strokesBefore.length)) {\n\t\t\t\t\tlet merged = withVectorFills(prev, [...vectorFills(prev), ...fills]);\n\t\t\t\t\tmerged = withVectorStrokes(merged, [...strokesBefore, ...vectorStrokes(n)]);\n\t\t\t\t\tout[out.length - 1] = merged;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tout.push(n);\n\t\t}\n\t\treturn out;\n\t};\n\n\t/**\n\t * A `<g data-vector-path>` from our own export (an aligned or arrowed stroke drawn in parts)\n\t * as the one path it came from: the fill part's outline and fill, the stroke part's paint and\n\t * line style, and the options the group's data attributes keep.\n\t */\n\tconst mergedPath = (g: Element, style: InheritedStyle, m: AffineMatrix, meta: Partial<VectorPathNode>): VectorPathNode | null => {\n\t\tconst part = (name: string) => g.children.find((c) => c.name === 'path' && c.attrs['data-vector-part'] === name);\n\t\tconst fillEl = part('fill');\n\t\tconst subpaths = fillEl ? shapeOf(fillEl) : null;\n\t\tif (!fillEl || !subpaths?.length) return null;\n\t\tconst a = g.attrs;\n\t\tconst fs = readStyle(fillEl, style);\n\t\tconst strokeEl = part('stroke');\n\t\tconst outlineEl = part('stroke-outline');\n\t\tconst ss = strokeEl ? readStyle(strokeEl, style) : null;\n\t\tconst width = length(a['data-vector-stroke-width'], ss ? length(ss.strokeWidth, 1) : 1);\n\t\tconst arrow = (v: string | undefined) => (VECTOR_ARROWHEADS.includes(v as VectorArrowhead) ? (v as VectorArrowhead) : undefined);\n\t\tconst scale = (v: string | undefined) => {\n\t\t\tconst n = v === undefined ? NaN : parseFloat(v);\n\t\t\treturn n > 0 ? n : undefined;\n\t\t};\n\t\tconst align = a['data-vector-stroke-align'];\n\t\tconst profile = widthProfileFromAttribute(a['data-vector-width-profile']);\n\t\tconst kept = (key: string, values: readonly string[]) => (values.includes(a[key]) ? a[key] : undefined);\n\t\tconst keptCap = kept('data-vector-stroke-linecap', ['round', 'square']) as VectorLineCap | undefined;\n\t\tconst keptJoin = kept('data-vector-stroke-linejoin', ['round', 'bevel']) as VectorLineJoin | undefined;\n\t\tconst keptDash = a['data-vector-stroke-dasharray'] ? numberList(a['data-vector-stroke-dasharray']) : undefined;\n\t\tconst node: VectorPathNode = {\n\t\t\tid: idFor(g),\n\t\t\ttype: 'path',\n\t\t\t...meta,\n\t\t\tsubpaths,\n\t\t\tfill: paint(fs.fill),\n\t\t\tstroke: ss ? paint(ss.stroke) : outlineEl ? paint(readStyle(outlineEl, style).fill) : null,\n\t\t\t...(ss ? strokeStyle(ss) : {}),\n\t\t\t...(fs.fillRule === 'evenodd' ? { fillRule: 'evenodd' as VectorFillRule } : {}),\n\t\t\tstrokeWidth: width,\n\t\t\t...(align === 'inside' || align === 'outside' ? { strokeAlign: align } : {}),\n\t\t\t...(arrow(a['data-vector-start-arrow']) ? { startArrow: arrow(a['data-vector-start-arrow']) } : {}),\n\t\t\t...(arrow(a['data-vector-end-arrow']) ? { endArrow: arrow(a['data-vector-end-arrow']) } : {}),\n\t\t\t...(a['data-vector-arrow-align'] === 'tip' ? { arrowAlign: 'tip' as const } : {}),\n\t\t\t...(scale(a['data-vector-start-arrow-scale']) ? { startArrowScale: scale(a['data-vector-start-arrow-scale']) } : {}),\n\t\t\t...(scale(a['data-vector-end-arrow-scale']) ? { endArrowScale: scale(a['data-vector-end-arrow-scale']) } : {}),\n\t\t\t...(profile.length ? { strokeWidthProfile: profile } : {}),\n\t\t\t...(keptCap ? { strokeLinecap: keptCap } : {}),\n\t\t\t...(keptJoin ? { strokeLinejoin: keptJoin } : {}),\n\t\t\t...(keptDash?.length ? { strokeDasharray: keptDash } : {})\n\t\t};\n\t\treturn placed(node, m);\n\t};\n\n\t/**\n\t * A `<g data-vector-live>` from our own export (path effects or a live boolean) as the node it\n\t * came from: its hidden source part with the effects put back (their offsets carried through\n\t * what import bakes into the geometry). The drawn appearance part is regenerated.\n\t */\n\tconst liveNode = (g: Element, style: InheritedStyle, m: AffineMatrix, meta: Pick<VectorNode, 'name' | 'opacity' | 'visible' | 'booleanOperation' | 'blendMode' | 'filters'>): VectorNode | null => {\n\t\tlet raw: Record<string, unknown> = {};\n\t\ttry {\n\t\t\traw = JSON.parse(g.attrs['data-vector-live']) ?? {};\n\t\t} catch {\n\t\t\traw = {};\n\t\t}\n\t\tconst sourceEl = g.children.find((c) => c.name === 'g' && c.attrs['data-vector-part'] === 'source');\n\t\tif (!sourceEl) return null;\n\t\tconst inner = visit(sourceEl, style, m);\n\t\tconst effects = sanitizeVectorEffects(raw.effects);\n\t\tconst liveBoolean = BOOLEAN_OPERATIONS.includes(raw.liveBoolean as VectorBooleanOperation) ? (raw.liveBoolean as VectorBooleanOperation) : null;\n\t\tconst id = idFor(g);\n\t\tif (raw.kind === 'path') {\n\t\t\tconst path = inner.find((n): n is VectorPathNode => n.type === 'path');\n\t\t\tif (!path) return null;\n\t\t\tconst { residual } = decomposeRigid(m);\n\t\t\tconst { visible: _v, ...rest } = path;\n\t\t\treturn { ...rest, id, ...meta, ...(effects.length ? { effects: transformVectorEffects(effects, residual) } : {}) };\n\t\t}\n\t\tif (!inner.length) return null;\n\t\tconst group: VectorGroupNode = { id, type: 'group', ...meta, children: inner };\n\t\tif (effects.length) group.effects = transformVectorEffects(effects, m);\n\t\tif (liveBoolean) group.liveBoolean = liveBoolean;\n\t\tconst blend = blendFromAttribute(g.attrs['data-vector-blend'], m);\n\t\tif (blend) group.blend = blend;\n\t\treturn group;\n\t};\n\n\tconst visit = (el: Element, style: InheritedStyle, matrix: AffineMatrix): VectorNode[] => {\n\t\tconst out: VectorNode[] = [];\n\t\tfor (const child of el.children) {\n\t\t\tif (SKIP.has(child.name)) continue;\n\t\t\tconst nodes = visitOne(child, style, matrix);\n\t\t\tout.push(...(nodes.length ? masked(child, nodes, multiplyMatrices(parseSvgTransform(child.attrs.transform), matrix)) : nodes));\n\t\t}\n\t\treturn options.mergeStackedShapes ? mergeStacked(out) : out;\n\t};\n\n\t/** The base style mask and clip content inherits (the document's, not the referencing element's). */\n\tlet rootStyle: InheritedStyle = { fill: '#000', stroke: 'none', strokeWidth: '' };\n\n\t/** Maps a unit box onto the bounds of `nodes` (`objectBoundingBox` units), or null without bounds. */\n\tconst bboxMatrix = (nodes: readonly VectorNode[]): AffineMatrix | null => {\n\t\tconst b = unionBounds(nodes.map((n) => vectorNodeBounds(n)));\n\t\treturn b && b.width > 0 && b.height > 0 ? [b.width, 0, 0, b.height, b.x, b.y] : null;\n\t};\n\n\t/** The nodes a `<clipPath>` or `<mask>` holds as one mask node (several are grouped), or null. */\n\tconst maskNode = (el: Element, m: AffineMatrix, nodes: readonly VectorNode[], units: string | undefined): VectorNode | null => {\n\t\tconst id = el.attrs.id;\n\t\tif (!id || resolving.has(id)) return null;\n\t\tlet space = m;\n\t\tif (units === 'objectBoundingBox') {\n\t\t\tconst box = bboxMatrix(nodes);\n\t\t\tif (!box) return null;\n\t\t\tspace = box;\n\t\t}\n\t\tresolving.add(id);\n\t\tconst isClip = el.name === 'clipPath';\n\t\tconst content = isClip ? clipRulesAsFillRules(el) : el;\n\t\tconst inner = visit(content, readStyle(content, rootStyle), multiplyMatrices(parseSvgTransform(el.attrs.transform), space));\n\t\tresolving.delete(id);\n\t\t// A clip's paint is not drawn: imported clips are unpainted outlines.\n\t\tconst strip = (n: VectorNode): VectorNode => (n.type === 'path' ? withVectorStrokes(withVectorFills({ ...n, fill: null, stroke: null }, []), []) : { ...n, children: n.children.map(strip) });\n\t\tconst list = isClip ? inner.map(strip) : inner;\n\t\tif (!list.length) return null;\n\t\treturn list.length === 1 ? list[0] : { id: idFor({ name: 'g', attrs: {}, children: [] }), type: 'group', name: isClip ? 'Clip' : 'Mask', children: list };\n\t};\n\n\t/**\n\t * Nodes made from an element that has `clip-path` and / or `mask`: wrapped in a mask group (the\n\t * clip or mask first), the clip inside the mask as SVG applies them. References to unknown or\n\t * unusable masks are dropped.\n\t */\n\tconst masked = (el: Element, nodes: VectorNode[], m: AffineMatrix): VectorNode[] => {\n\t\tlet out = nodes;\n\t\tconst clipEl = rawMasks.get(urlId(attrOrStyle(el, 'clip-path')) ?? '');\n\t\tif (clipEl?.name === 'clipPath') {\n\t\t\tconst source = maskNode(clipEl, m, out, clipEl.attrs.clipPathUnits);\n\t\t\tif (source) out = [{ id: idFor({ name: 'g', attrs: {}, children: [] }), type: 'group', name: 'Clip Group', mask: { type: 'clip' }, children: [source, ...out] }];\n\t\t}\n\t\tconst maskEl = rawMasks.get(urlId(attrOrStyle(el, 'mask')) ?? '');\n\t\tif (maskEl?.name === 'mask') {\n\t\t\tconst source = maskNode(maskEl, m, out, maskEl.attrs.maskContentUnits);\n\t\t\tconst type = attrOrStyle(maskEl, 'mask-type') === 'alpha' ? 'alpha' : 'luminance';\n\t\t\tif (source) out = [{ id: idFor({ name: 'g', attrs: {}, children: [] }), type: 'group', name: 'Opacity Mask', mask: { type }, children: [source, ...out] }];\n\t\t}\n\t\treturn out;\n\t};\n\n\t/**\n\t * A `<g data-vector-mask>` from our own export as the mask group it came from: the mask from its\n\t * hidden source part or its `<mask>`, the contents from the masked part (a `<id>~content` live\n\t * group there gives back the group's effects).\n\t */\n\tconst maskGroup = (g: Element, style: InheritedStyle, m: AffineMatrix, meta: Partial<VectorGroupNode>): VectorNode | null => {\n\t\tlet raw: unknown = null;\n\t\ttry {\n\t\t\traw = JSON.parse(g.attrs['data-vector-mask']);\n\t\t} catch {\n\t\t\traw = null;\n\t\t}\n\t\tconst mask = sanitizeVectorMask(raw);\n\t\tconst part = (name: string) => g.children.find((c) => c.name === 'g' && c.attrs['data-vector-part'] === name);\n\t\tconst maskedEl = part('masked');\n\t\tif (!mask || !maskedEl) return null;\n\t\tconst id = idFor(g);\n\t\tlet sourceEl = part('mask-source');\n\t\tif (!sourceEl) {\n\t\t\tconst el = rawMasks.get(urlId(maskedEl.attrs.mask) ?? '');\n\t\t\tconst invert = el?.children.find((c) => c.name === 'g' && c.attrs['data-vector-part'] === 'mask-invert');\n\t\t\tsourceEl = invert ?? el;\n\t\t}\n\t\tconst source = sourceEl ? visit(sourceEl, style, m)[0] : undefined;\n\t\tlet contents = visit(maskedEl, style, m);\n\t\tlet effects: VectorEffect[] | undefined;\n\t\tconst wrapper = contents.length === 1 && contents[0].type === 'group' && contents[0].id === `${id}~content` ? contents[0] : null;\n\t\tif (wrapper?.type === 'group') {\n\t\t\teffects = wrapper.effects ?? undefined;\n\t\t\tcontents = wrapper.children;\n\t\t}\n\t\tif (!source) return contents.length ? { id, type: 'group', ...meta, children: contents } : null;\n\t\treturn { id, type: 'group', ...meta, mask, ...(effects?.length ? { effects } : {}), children: [source, ...contents] };\n\t};\n\n\tconst visitOne = (child: Element, style: InheritedStyle, matrix: AffineMatrix): VectorNode[] => {\n\t\tconst out: VectorNode[] = [];\n\t\tconst m = multiplyMatrices(parseSvgTransform(child.attrs.transform), matrix);\n\t\tconst s = readStyle(child, style);\n\t\tconst opacity = child.attrs.opacity !== undefined ? parseFloat(child.attrs.opacity) : undefined;\n\t\tconst hidden = child.attrs.display === 'none' || child.attrs.visibility === 'hidden';\n\t\tconst blendMode = vectorBlendModeFromCss(s.mixBlendMode);\n\t\tconst filters = filtersOf(s.filter);\n\t\tconst meta = {\n\t\t\t...(child.attrs['inkscape:label'] || child.attrs['data-name'] ? { name: child.attrs['inkscape:label'] ?? child.attrs['data-name'] } : {}),\n\t\t\t...(opacity !== undefined && Number.isFinite(opacity) && opacity < 1 ? { opacity } : {}),\n\t\t\t...(hidden ? { visible: false } : {}),\n\t\t\t...(blendMode && blendMode !== 'normal' ? { blendMode } : {}),\n\t\t\t...(filters.length ? { filters } : {})\n\t\t};\n\t\t// A blend's in-betweens are regenerated from its keys.\n\t\tif (child.attrs['data-vector-blend-step'] !== undefined) return out;\n\t\tconst operation = child.attrs['data-vector-boolean-operation'];\n\t\tif (BOOLEAN_OPERATIONS.includes(operation as VectorBooleanOperation)) (meta as { booleanOperation?: VectorBooleanOperation }).booleanOperation = operation as VectorBooleanOperation;\n\t\tif (child.name === 'g' && child.attrs['data-vector-mask'] !== undefined) {\n\t\t\tconst node = maskGroup(child, s, m, meta);\n\t\t\tif (node) out.push(node);\n\t\t\treturn out;\n\t\t}\n\t\tif (child.name === 'g' && child.attrs['data-vector-live'] !== undefined) {\n\t\t\tconst node = liveNode(child, s, m, meta);\n\t\t\tif (node) out.push(node);\n\t\t\treturn out;\n\t\t}\n\t\tif (child.name === 'g' && child.attrs['data-vector-path'] !== undefined && child.attrs['data-vector-fills'] !== undefined) {\n\t\t\tconst node = stackedPath(child, s, m, meta);\n\t\t\tif (node) out.push(node);\n\t\t\treturn out;\n\t\t}\n\t\tif (child.name === 'g' && child.attrs['data-vector-path'] !== undefined) {\n\t\t\tconst node = mergedPath(child, s, m, meta);\n\t\t\tif (node) out.push(node);\n\t\t\treturn out;\n\t\t}\n\t\tif (child.name === 'g' || child.name === 'svg' || child.name === 'a' || child.name === 'switch') {\n\t\t\tconst children = visit(child, s, m);\n\t\t\tif (!children.length) return out;\n\t\t\tconst group: VectorGroupNode = { id: idFor(child), type: 'group', ...meta, children };\n\t\t\tconst blend = child.name === 'g' ? blendFromAttribute(child.attrs['data-vector-blend'], m) : null;\n\t\t\tif (blend) group.blend = blend;\n\t\t\tout.push(group);\n\t\t\treturn out;\n\t\t}\n\t\tconst subpaths = shapeOf(child);\n\t\tif (!subpaths || !subpaths.length) return out;\n\t\tconst node = placed(\n\t\t\twithOpacities(\n\t\t\t\t{\n\t\t\t\t\tid: idFor(child),\n\t\t\t\t\ttype: 'path',\n\t\t\t\t\t...meta,\n\t\t\t\t\tsubpaths,\n\t\t\t\t\tfill: child.name === 'line' ? null : paint(s.fill),\n\t\t\t\t\tstroke: paint(s.stroke),\n\t\t\t\t\t...strokeStyle(s)\n\t\t\t\t},\n\t\t\t\ts\n\t\t\t),\n\t\t\tm\n\t\t);\n\t\tout.push(node);\n\t\treturn out;\n\t};\n\n\trootStyle = readStyle(root, rootStyle);\n\tconst nodes = visit(root, rootStyle, rootMatrix);\n\treturn { width, height, nodes };\n}\n"]}
|