@pixerate/editor 0.42.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.md +8 -0
- package/dist/canvas/index.d.cts +2 -2
- package/dist/canvas/index.d.ts +2 -2
- package/dist/{pathfinder-D7hXenzu.d.ts → pathfinder-D98S2lV5.d.ts} +1 -1
- package/dist/{pathfinder-BSW2ZTO6.d.cts → pathfinder-DKRcXiLS.d.cts} +1 -1
- package/dist/vector/index.cjs +2041 -424
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +606 -18
- package/dist/vector/index.d.ts +606 -18
- package/dist/vector/index.js +1820 -203
- package/dist/vector/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/vector/index.cjs","../../src/vector/types.ts","../../src/vector/corners.ts","../../src/vector/width.ts","../../src/vector/varstroke.ts","../../src/vector/stroke.ts","../../src/vector/pathEffects.ts","../../src/vector/appearance.ts","../../src/vector/geometry.ts","../../src/vector/svg.ts","../../src/vector/blend.ts","../../src/vector/blendGroup.ts","../../src/vector/snap.ts","../../src/vector/pathEdit.ts","../../src/vector/pathCut.ts","../../src/vector/ink.ts","../../src/vector/builder.ts","../../src/vector/controller.ts","../../src/vector/render.ts","../../src/vector/commands.ts"],"names":["t","v","sides","p","sub","len","w","cross","dot","polygon","near","pt","at","s","dist","EPS","unit","add","KAPPA","out","emit","live","corners","width","node","along","plain","sx","ang","c","STYLE_KEYS","rail","toVec","crossing","length","frame","movesX","startTangent","endTangent","onAnchor","endOf","contains","usable","degenerate","face","line","last","editable","toScreen","toPoint","flatten","clean","key","ref","list"],"mappings":"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A,QAAA;AACA,QAAA;AAEC,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGS;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACH,EAAA;AACE,IAAA;AACF,IAAA;AACN,IAAA;AACQ,IAAA;AACP,EAAA;AACH;AAGS;AACF,EAAA;AACF,EAAA;AACE,EAAA;AACC,IAAA;AACD,IAAA;AACL,IAAA;AACM,IAAA;AAEA,IAAA;AACN,IAAA;AACA,EAAA;AACI,EAAA;AACK,EAAA;AACX;AAEM;AAOU;AACT,EAAA;AACG,EAAA;AACL,EAAA;AACK,EAAA;AACA,EAAA;AACJ,EAAA;AACC,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAEM;AAGU;AACL,EAAA;AACA,EAAA;AACH,IAAA;AACN,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAOS;AACF,EAAA;AACD,IAAA;AACE,IAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACQ,IAAA;AACR,IAAA;AACA,IAAA;AACG,IAAA;AACM,EAAA;AACD,EAAA;AACV;AAGS;AACD,EAAA;AACG,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACV,EAAA;AACD;AAGgB;AACR,EAAA;AACR;AAEM;AAQU;AACV,EAAA;AACC,EAAA;AACG,EAAA;AACL,EAAA;AACC,EAAA;AACE,IAAA;AACE,IAAA;AACF,EAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,MAAA;AACD,QAAA;AACJ,QAAA;AACG,QAAA;AACC,QAAA;AACJ,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACO,EAAA;AACR;AAGgB;AACX,EAAA;AACE,EAAA;AACC,IAAA;AACF,IAAA;AACG,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAMgB;AACV,EAAA;AACC,EAAA;AACA,EAAA;AACN,EAAA;AACM,IAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACJ,IAAA;AACK,IAAA;AACC,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAOgB;AACT,EAAA;AACA,EAAA;AACC,EAAA;AACA,IAAA;AACA,IAAA;AACL,MAAA;AACM,IAAA;AACP,EAAA;AACF;AAOgB;AACT,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACL,IAAA;AACO,MAAA;AACF,MAAA;AACH,QAAA;AACI,QAAA;AACL,MAAA;AACM,MAAA;AACP,IAAA;AACD,EAAA;AACM,EAAA;AACC,EAAA;AACR;APotCY;AACA;AQh1DA;AAMC;AACP,EAAA;AACA,EAAA;AACM,EAAA;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACP;AAEa;AAGG;AACR,EAAA;AACR;AAEgB;AACR,EAAA;AACR;AAGgB;AACX,EAAA;AACA,EAAA;AACG,IAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAEM;AACA;AAOU;AACL,EAAA;AACC,EAAA;AACD,EAAA;AACX;AAGgB;AACL,EAAA;AACA,EAAA;AACN,EAAA;AACE,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAMgB;AACT,EAAA;AACN,EAAA;AACQ,IAAA;AACD,IAAA;AACF,IAAA;AACE,IAAA;AACN,IAAA;AACO,MAAA;AACF,MAAA;AACA,MAAA;AACC,MAAA;AACN,IAAA;AACO,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAGgB;AACX,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACAV,EAAAA;AACAC,EAAAA;AACG,EAAA;AACF,EAAA;AACR;AAOgB;AACT,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACN,EAAA;AACS,IAAA;AACF,MAAA;AACJ,QAAA;AACI,QAAA;AACJ,QAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACJ,QAAA;AACG,QAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACI,MAAA;AACA,QAAA;AACJ,QAAA;AACA,QAAA;AACI,QAAA;AACH,UAAA;AACA,UAAA;AACA,UAAA;AACC,YAAA;AACA,YAAA;AACD,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACC,IAAA;AACK,MAAA;AACA,MAAA;AACE,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAOgB;AACT,EAAA;AACL,IAAA;AACI,MAAA;AACC,MAAA;AACL,IAAA;AACD,EAAA;AACM,EAAA;AACP;AAGgB;AACf,EAAA;AACK,IAAA;AACA,IAAA;AACG,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGgB;AACT,EAAA;AACL,IAAA;AACO,MAAA;AACF,MAAA;AACA,MAAA;AACH,QAAA;AACI,QAAA;AACL,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGgB;AACN,EAAA;AACH,EAAA;AACN,EAAA;AACO,IAAA;AACN,EAAA;AACM,EAAA;AACR;AAOgB;AACR,EAAA;AACR;AAMgB;AACL,EAAA;AACJ,EAAA;AACC,EAAA;AACR;AAOgB;AACR,EAAA;AACE,EAAA;AACF,IAAA;AACC,IAAA;AACR,EAAA;AACU,EAAA;AACF,EAAA;AACC,EAAA;AACV;AAGgB;AACX,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACS,IAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACR,EAAA;AACI,EAAA;AACK,EAAA;AACV;AAGgB;AACN,EAAA;AACV;AAGgB;AACN,EAAA;AACV;AAoBgB;AACN,EAAA;AAEH,EAAA;AACG,EAAA;AACF,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACG,EAAA;AACH,EAAA;AACF,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAMgB;AACV,EAAA;AACE,EAAA;AACR;AAGgB;AACN,EAAA;AACF,IAAA;AACF,IAAA;AACA,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAYgB;AACX,EAAA;AACC,EAAA;AACE,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACD,MAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,EAAA;AACM,EAAA;AACR;AAMa;AAGG;AACR,EAAA;AACA,EAAA;AACC,EAAA;AACT;AAEa;AAEG;AACP,EAAA;AACT;AAGgB;AACJ,EAAA;AACL,EAAA;AACA,EAAA;AACE,EAAA;AACT;AAEgB;AACN,EAAA;AACV;AAEgB;AACR,EAAA;AACR;AAGgB;AACL,EAAA;AACJ,EAAA;AACD,EAAA;AACG,EAAA;AACT;AAGgB;AACN,EAAA;AACV;AAOgB;AACL,EAAA;AACJ,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAOgB;AACL,EAAA;AACJ,EAAA;AACF,EAAA;AACO,EAAA;AACL,EAAA;AACA,EAAA;AACC,EAAA;AACE,IAAA;AACR,IAAA;AACD,EAAA;AACD;AAES;AACE,EAAA;AACJ,EAAA;AACI,EAAA;AACC,EAAA;AACL,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAGS;AACJ,EAAA;AACK,EAAA;AACA,EAAA;AACF,IAAA;AACA,IAAA;AAEA,IAAA;AACD,IAAA;AACC,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AAEA,EAAA;AACC,EAAA;AACH,IAAA;AACC,IAAA;AACJ,IAAA;AACC,MAAA;AACA,MAAA;AACC,IAAA;AACH,EAAA;AACD;AAOgB;AACT,EAAA;AACE,EAAA;AACA,EAAA;AACF,EAAA;AACC,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAGgB;AACT,EAAA;AACL,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACL,QAAA;AACI,QAAA;AACL,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGgB;AACT,EAAA;AACA,EAAA;AACD,EAAA;AACE,EAAA;AACR;AAOgB;AACT,EAAA;AACI,EAAA;AACA,EAAA;AACD,EAAA;AACA,EAAA;AACF,EAAA;AACR;AAMgB;AACT,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACAH,EAAAA;AACF,EAAA;AACK,EAAA;AACF,IAAA;AACA,IAAA;AACD,IAAA;AACN,EAAA;AACI,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAOgB;AACT,EAAA;AACI,EAAA;AACF,EAAA;AACJ,EAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACR,EAAA;AACU,EAAA;AACJ,EAAA;AACA,EAAA;AACiB,EAAA;AACnB,EAAA;AACA,EAAA;AACC,EAAA;AACK,EAAA;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACD,EAAA;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACK,EAAA;AACA,EAAA;AACV;AAMgB;AACT,EAAA;AACI,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACD,EAAA;AACA,EAAA;AACE,EAAA;AACC,IAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACR,IAAA;AACA,IAAA;AACD,EAAA;AACM,EAAA;AACE,EAAA;AACC,EAAA;AACV;AAMgB;AACT,EAAA;AACG,EAAA;AACV;AAGgB;AACT,EAAA;AAIE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACP,EAAA;AACO,EAAA;AACV;AAOaa;AAMG;AACP,EAAA;AACF,EAAA;AACC,EAAA;AACR;AAGgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACE,IAAA;AACR,IAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACN,IAAA;AACD,EAAA;AACD;AAGgB;AACL,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACE,IAAA;AACT,EAAA;AACS,EAAA;AACV;AAGgB;AACL,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACT,EAAA;AACS,EAAA;AACV;AAGgB;AACN,EAAA;AACV;AAGgB;AACL,EAAA;AACA,EAAA;AACJ,EAAA;AACC,EAAA;AACE,IAAA;AACR,IAAA;AACC,EAAA;AACH;ARioDY;AACA;ASryEI;AACT,EAAA;AACG,EAAA;AAAa,IAAA;AAEf,MAAA;AACD,MAAA;AACJ,IAAA;AACF,EAAA;AACO,EAAA;AACR;AAGgB;AACf,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACJ,IAAA;AACH,EAAA;AACO,EAAA;AACR;AAEM;AAGU;AACV,EAAA;AACD,EAAA;AACK,EAAA;AACV;AAwBM;AAGU;AACV,EAAA;AACC,EAAA;AACF,EAAA;AACE,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAES;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACN,EAAA;AACM,EAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACC,IAAA;AACJ,IAAA;AACI,IAAA;AACH,IAAA;AACJ,IAAA;AACO,IAAA;AACR,EAAA;AACD;AAGgB;AACR,EAAA;AACR;AAGgB;AACV,EAAA;AACE,EAAA;AACA,IAAA;AACC,MAAA;AACN,MAAA;AACA,IAAA;AACF,EAAA;AACD;AAEM;AAEG;AACF,EAAA;AACA,EAAA;AAGA,EAAA;AAGC,EAAA;AACR;AAMS;AACH,EAAA;AACD,EAAA;AACA,EAAA;AACG,IAAA;AACC,EAAA;AACA,IAAA;AACR,EAAA;AACK,EAAA;AACK,EAAA;AACJ,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACC,IAAA;AACF,IAAA;AACL,EAAA;AACM,EAAA;AACF,EAAA;AACG,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAGgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AACDC,IAAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACGA,IAAAA;AACR,EAAA;AAEMC,EAAAA;AACD,IAAA;AACA,IAAA;AAEGC,MAAAA;AACL,QAAA;AACI,QAAA;AACA,QAAA;AACL,MAAA;AACM,MAAA;AACA,MAAA;AACN,MAAA;AACM,MAAA;AACA,MAAA;AACF,MAAA;AACH,QAAA;AACAD,QAAAA;AACM,MAAA;AACD,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACI,IAAA;AACG,MAAA;AACN,MAAA;AACM,MAAA;AACN,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACAE,IAAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACDH,MAAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACJ,MAAA;AACD,IAAA;AACM,IAAA;AACD,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AAEI,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACF,IAAA;AAEH,MAAA;AACI,MAAA;AACA,MAAA;AACA,MAAA;AACL,IAAA;AACM,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACA,MAAA;AACA,IAAA;AACF,MAAA;AACA,MAAA;AACH,QAAA;AACA,QAAA;AACI,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACI,MAAA;AACC,MAAA;AACL,MAAA;AACD,IAAA;AACM,IAAA;AACP,EAAA;AAEA,EAAA;AACU,EAAA;AACA,EAAA;AACJ,EAAA;AACA,EAAA;AACG,EAAA;AACD,EAAA;AACH,IAAA;AACL,EAAA;AACS,EAAA;AACF,EAAA;AACR;AAMM;AAcG;AACF,EAAA;AAKA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACI,EAAA;AACA,IAAA;AACD,IAAA;AACF,IAAA;AACH,MAAA;AACK,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACM,IAAA;AACF,IAAA;AACC,IAAA;AACG,IAAA;AACF,IAAA;AACA,IAAA;AACD,IAAA;AACN,EAAA;AACM,EAAA;AACE,IAAA;AACP,IAAA;AACO,MAAA;AACC,MAAA;AACR,IAAA;AACO,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAES;AAEN,EAAA;AAOH;AAEM;AAQA;AACK,EAAA;AACA,EAAA;AACH,EAAA;AACR;AAGgB;AACO,EAAA;AACjB,EAAA;AACC,EAAA;AACF,EAAA;AACI,EAAA;AACD,IAAA;AACF,IAAA;AACI,IAAA;AACF,MAAA;AACA,QAAA;AACJ,QAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACF,IAAA;AAEI,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAYM;AACC,EAAA;AACE,EAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAES;AACF,EAAA;AACN,EAAA;AACA,EAAA;AACO,IAAA;AACE,IAAA;AACF,IAAA;AACG,IAAA;AACV,EAAA;AACO,EAAA;AACR;AAES;AACE,EAAA;AAGF,IAAA;AACF,MAAA;AAIJ,IAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACN,MAAA;AACA,MAAA;AACI,MAAA;AACL,IAAA;AACA,EAAA;AACH;AAEM;AACK,EAAA;AACA,EAAA;AACL,EAAA;AACI,EAAA;AACV;AAES;AACA,EAAA;AACR,EAAA;AACD;AAES;AACF,EAAA;AACD,EAAA;AACI,EAAA;AACL,EAAA;AACE,EAAA;AACD,EAAA;AACA,EAAA;AACE,EAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AACG,EAAA;AACV;AAGgB;AACT,EAAA;AACK,EAAA;AACP,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACF,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACI,IAAA;AACG,IAAA;AACC,IAAA;AACD,IAAA;AACR,EAAA;AAEM,EAAA;AACN,EAAA;AACM,EAAA;AACClB,IAAAA;AACA,IAAA;AACG,IAAA;AACLA,IAAAA;AACGA,IAAAA;AACR,EAAA;AAEM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEA,EAAA;AAEA,EAAA;AACC,IAAA;AACE,IAAA;AACF,MAAA;AAEA,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACJ,QAAA;AACA,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACJ,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACJ,QAAA;AACA,QAAA;AACI,QAAA;AACJ,QAAA;AACD,MAAA;AACK,MAAA;AACJ,QAAA;AACI,MAAA;AACA,MAAA;AACJ,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACO,IAAA;AACR,EAAA;AAEM,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AAGM,EAAA;AACD,IAAA;AACE,IAAA;AACA,IAAA;AACF,IAAA;AACG,IAAA;AACR,EAAA;AAOM,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACAsB,IAAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACN,MAAA;AACD,IAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,MAAA;AACE,MAAA;AACH,MAAA;AACH,MAAA;AACM,MAAA;AACN,MAAA;AACI,MAAA;AACG,MAAA;AACP,MAAA;AACI,MAAA;AACA,MAAA;AACA,MAAA;AACE,MAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AAOM,EAAA;AACD,IAAA;AACA,IAAA;AACG,MAAA;AACC,IAAA;AACD,MAAA;AACP,IAAA;AACM,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACD,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACK,IAAA;AACC,IAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACF,IAAA;AACG,IAAA;AACR,EAAA;AAEM,EAAA;AACC,IAAA;AACN,IAAA;AACK,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACL,MAAA;AAEI,MAAA;AACE,MAAA;AACF,MAAA;AACA,MAAA;AACH,QAAA;AACIC,QAAAA;AACJ,QAAA;AACD,MAAA;AACI,MAAA;AACH,QAAA;AACIA,QAAAA;AACJ,QAAA;AACD,MAAA;AACI,MAAA;AACH,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACI,QAAA;AACA,QAAA;AACJ,QAAA;AACD,MAAA;AACM,MAAA;AACD,MAAA;AACC,MAAA;AACL,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AAEM,EAAA;AACG,EAAA;AACV;ATypEY;AACA;AUz2FE;AACA;AACDvB;AACC;AAYR;AAGU;AACL,EAAA;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACL,EAAA;AACS,EAAA;AACV;AAGS;AACF,EAAA;AACA,EAAA;AACAI,EAAAA;AACA,EAAA;AACFA,EAAAA;AACE,EAAA;AACAoB,EAAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACR;AAGgB;AACT,EAAA;AACA,EAAA;AAEA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACA,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACI,IAAA;AACR,IAAA;AACD,EAAA;AACA,EAAA;AACO,IAAA;AACF,IAAA;AACL,EAAA;AACM,EAAA;AACG,EAAA;AACV;AAGS;AACF,EAAA;AACA,EAAA;AACE,EAAA;AACD,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACAC,IAAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACArB,IAAAA;AACFqB,IAAAA;AACC,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAEc;AAQE;AACT,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AAEE,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACA,EAAA;AACG,EAAA;AACJ,EAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACJ,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACK,MAAA;AACN,IAAA;AACI,IAAA;AACE,IAAA;AACE,IAAA;AACR,EAAA;AACQ,EAAA;AACV;AAGgB;AACN,EAAA;AACV;AAGgB;AACL,EAAA;AACH,EAAA;AACD,EAAA;AACI,EAAA;AACJ,EAAA;AACG,EAAA;AACV;AAGS;AACE,EAAA;AACJ,EAAA;AACC,EAAA;AACR;AAGS;AACA,EAAA;AACC,EAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACT,EAAA;AACO,EAAA;AACR;AAOS;AACE,EAAA;AACD,EAAA;AACA,EAAA;AACT,EAAA;AACS,IAAA;AACA,IAAA;AACT,EAAA;AACM,EAAA;AACA,EAAA;AACE,EAAA;AACR,EAAA;AACS,EAAA;AACV;AAEc;AACA;AAER;AAOG;AACE,EAAA;AACAzB,EAAAA;AACD,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACJ,IAAA;AACK,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AAEA,MAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACE,MAAA;AACF,MAAA;AACD,IAAA;AACI,IAAA;AACG,IAAA;AACP,EAAA;AACF;AAES;AACA,EAAA;AACC,EAAA;AACF,IAAA;AACE,IAAA;AACT,EAAA;AACO,EAAA;AACR;AASS;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACJ,EAAA;AACDO,IAAAA;AACAD,IAAAA;AACA,IAAA;AACJ,IAAA;AACO,MAAA;AACA,MAAA;AACA,MAAA;AACNC,MAAAA;AACAD,MAAAA;AACI,MAAA;AACL,IAAA;AAEM,IAAA;AACA,IAAA;AACC,IAAA;AACR,EAAA;AACU,EAAA;AAEJ,EAAA;AACF,EAAA;AACK,EAAA;AACF,IAAA;AACE,IAAA;AACT,EAAA;AACI,EAAA;AACG,IAAA;AACN,IAAA;AACC,MAAA;AACC,QAAA;AACI,QAAA;AACL,MAAA;AACF,EAAA;AACO,EAAA;AACR;AAGS;AACE,EAAA;AACO,EAAA;AACZ,EAAA;AACC,EAAA;AACA,EAAA;AACE,EAAA;AACT;AAES;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACT,EAAA;AACM,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACS,EAAA;AACV;AAcgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,IAAA;AACR,IAAA;AACO,IAAA;AACR,EAAA;AAEM,EAAA;AACC,EAAA;AACE,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACJ,IAAA;AACE,IAAA;AACC,MAAA;AACN,MAAA;AACD,IAAA;AACI,IAAA;AACG,MAAA;AACA,MAAA;AACA,MAAA;AACF,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACG,MAAA;AACL,IAAA;AAAiI,IAAA;AAElI,EAAA;AAEM,EAAA;AACD,IAAA;AACA,IAAA;AACG,IAAA;AACR,EAAA;AACM,EAAA;AAIA,EAAA;AACA,EAAA;AAKF,EAAA;AACJ,EAAA;AACO,IAAA;AACA,IAAA;AACF,IAAA;AACE,IAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACE,MAAA;AACF,QAAA;AACA,QAAA;AACH,QAAA;AACD,MAAA;AACM,MAAA;AAEA,MAAA;AACF,MAAA;AACH,QAAA;AACI,QAAA;AACC,QAAA;AACL,QAAA;AACM,MAAA;AACJ,MAAA;AACA,MAAA;AACH,IAAA;AACI,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAMgB;AACX,EAAA;AACM,EAAA;AACD,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACC,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACL,MAAA;AACF,EAAA;AACO,EAAA;AACR;AAcS;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACLC,EAAAA;AACAD,EAAAA;AACK,EAAA;AACF,IAAA;AACA,IAAA;AACE,IAAA;AACD,IAAA;AACPA,IAAAA;AACD,EAAA;AACS,EAAA;AACV;AAOgB;AAML,EAAA;AACJ,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACE,IAAA;AACT,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACA,IAAA;AACM,IAAA;AACP,EAAA;AACF;AAUgB;AACT,EAAA;AACCoB,IAAAA;AACAC,IAAAA;AACAC,IAAAA;AACAhB,IAAAA;AACC,IAAA;AACR,EAAA;AACU,EAAA;AACA,EAAA;AACN,EAAA;AACO,EAAA;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACF,EAAA;AACT;AAOgB;AACN,EAAA;AACA,EAAA;AACF,EAAA;AACR;AAES;AACC,EAAA;AACV;AAGS;AACH,EAAA;AACC,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACD,EAAA;AACG,EAAA;AACA,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAGgB;AACT,EAAA;AACC,EAAA;AACA,IAAA;AACC,IAAA;AACP,EAAA;AACF;AAES;AACF,EAAA;AACC,EAAA;AACR;AAGS;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACG,IAAA;AACA,IAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACM,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACG,IAAA;AACP,EAAA;AACK,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAGS;AACC,EAAA;AACV;AAOgB;AACN,EAAA;AACA,EAAA;AACH,EAAA;AACA,EAAA;AACD,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACC,EAAA;AACA,EAAA;AACI,EAAA;AACF,EAAA;AACA,EAAA;AACD,EAAA;AACR;AAGgB;AACP,EAAA;AACFD,EAAAA;AACA,EAAA;AACI,EAAA;AACH,IAAA;AACA,IAAA;AACC,IAAA;AACP,EAAA;AACF;AAGS;AACF,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACN,IAAA;AACO,IAAA;AACR,EAAA;AACU,EAAA;AACA,EAAA;AACJ,EAAA;AACG,EAAA;AACV;AAEM;AAMU;AACTF,EAAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACE,EAAA;AACN,EAAA;AACA,EAAA;AACO,IAAA;AACD,EAAA;AACI,EAAA;AACJ,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAGS;AACF,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAEMoB;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAES;AACF,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAMS;AACD,EAAA;AAGR;AAMS;AAOF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAEC,MAAA;AACN,MAAA;AACA,IAAA;AACK,IAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACG,IAAA;AACR,EAAA;AACD;AAGS;AACE,EAAA;AACD,EAAA;AACC,EAAA;AACH,EAAA;AAIR;AAGM;AAGG;AACF,EAAA;AACW,EAAA;AACX,EAAA;AACA,EAAA;AACI,EAAA;AACJ,EAAA;AACE,EAAA;AACC,EAAA;AACV;AAOgB;AACT,EAAA;AACClB,IAAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACF,IAAA;AACA,IAAA;AACAR,IAAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACD,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACO,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACD,IAAA;AACE,MAAA;AACL,QAAA;AACA,QAAA;AACI,QAAA;AACC,QAAA;AACL,QAAA;AACA,MAAA;AACK,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAMgB;AACR,EAAA;AACR;AASM;AAOU;AACT,EAAA;AACI,EAAA;AACJ,EAAA;AACA,EAAA;AACF,EAAA;AACI,EAAA;AACD,EAAA;AACG,EAAA;AACA,EAAA;AACN,EAAA;AACE,EAAA;AACA,EAAA;AACI,EAAA;AACN,EAAA;AACC,EAAA;AACD,EAAA;AACC,EAAA;AACE,EAAA;AACR;AVmpFY;AACA;AW/jHC;AACA;AAEA;AAEA;AAGG;AACN,EAAA;AACV;AAGgB;AACR,EAAA;AACR;AAGgB;AACR,EAAA;AACN,IAAA;AACO,IAAA;AACP,IAAA;AACA,IAAA;AACQ,IAAA;AACT,EAAA;AACD;AAMS;AACJ,EAAA;AACK,EAAA;AACD,EAAA;AACT;AAGgB;AACT,EAAA;AACA,EAAA;AACD,EAAA;AACG,EAAA;AACR,EAAA;AACO,IAAA;AACA,IAAA;AACH,IAAA;AACI,MAAA;AACF,MAAA;AACE,MAAA;AACA,MAAA;AACD,MAAA;AACD,MAAA;AACJ,IAAA;AACF,EAAA;AACO,EAAA;AACR;AAEM;AACK,EAAA;AACH,EAAA;AACR;AAGgB;AACL,EAAA;AACJ,EAAA;AACC,IAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACC,IAAA;AACF,IAAA;AAEE,IAAA;AACA,IAAA;AACAU,IAAAA;AAIC,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAgBgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACF,IAAA;AACA,IAAA;AACG,IAAA;AACR,EAAA;AACM,EAAA;AACG,EAAA;AACF,IAAA;AACA,IAAA;AACF,IAAA;AACJ,IAAA;AACD,EAAA;AACQ,EAAA;AACF,EAAA;AACG,EAAA;AACA,EAAA;AACV;AAGgB;AACXT,EAAAA;AACK,EAAA;AACH,EAAA;AACG,EAAA;AACA,EAAA;AACF,EAAA;AACA,IAAA;AACF,IAAA;AACC,IAAA;AACN,EAAA;AACM,EAAA;AACI,EAAA;AACA,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAMgB;AACT,EAAA;AACG,EAAA;AACH,EAAA;AACF,EAAA;AACG0B,IAAAA;AACFA,IAAAA;AACG,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACH,EAAA;AACG,EAAA;AACV;AAGgB;AACL,EAAA;AACH,EAAA;AACR;AAMM;AAEG;AACF,EAAA;AACA,EAAA;AACF,EAAA;AACC,EAAA;AACD,EAAA;AACC,EAAA;AACD,EAAA;AACC,EAAA;AACE,EAAA;AACR;AAEM;AAGG;AACF,EAAA;AAEG,EAAA;AACV;AAMgB;AACT,EAAA;AACA,EAAA;AACD,EAAA;AACC,EAAA;AACD,EAAA;AACC,EAAA;AACF,EAAA;AACC,EAAA;AACA,IAAA;AACE,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACO,MAAA;AACF,MAAA;AACA,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACA,QAAA;AACH,UAAA;AACA,UAAA;AACC,YAAA;AACA,YAAA;AACA,YAAA;AACD,UAAA;AACD,QAAA;AACI,QAAA;AACL,MAAA;AACI,MAAA;AACL,IAAA;AACA,EAAA;AACM,EAAA;AACR;AAEM;AAEA;AAEG;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACA,EAAA;AACC,EAAA;AACJ,IAAA;AACI,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAOgB;AACX,EAAA;AACE,EAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACG,MAAA;AACF,MAAA;AACL,IAAA;AACI,IAAA;AACG,IAAA;AACP,EAAA;AACM,EAAA;AACR;AAGSL;AACA,EAAA;AACF,EAAA;AACF,EAAA;AACG,EAAA;AACR;AAGgB;AACP,EAAA;AACC,EAAA;AACV;AAMgB;AACL,EAAA;AACJ,EAAA;AACA,EAAA;AAGA,EAAA;AACD,EAAA;AACC,EAAA;AACE,EAAA;AACT;AXg/GY;AACA;AYntHC;AAGG;AACV,EAAA;AACC,EAAA;AACA,EAAA;AACE,EAAA;AACF,IAAA;AACJ,MAAA;AACI,IAAA;AACE,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACOrB,MAAAA;AACA,MAAA;AACN,MAAA;AA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;AACJ,EAAA;AACE,EAAA;AACT;AAUS;AACE,EAAA;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD,IAAA;AACF,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACC,IAAA;AACF,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACN,IAAA;AACD,EAAA;AACU,EAAA;AACX;AAOS;AACF,EAAA;AACC,IAAA;AACN,IAAA;AACA,IAAA;AACO,IAAA;AACR,EAAA;AACO,EAAA;AACR;AASgB;AACT,EAAA;AACI,EAAA;AACN,EAAA;AACI,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACM,MAAA;AACA,MAAA;AACA,MAAA;AACD,MAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACQ,EAAA;AACJ,EAAA;AACC,EAAA;AAEI,IAAA;AACF,IAAA;AACE,IAAA;AACT,EAAA;AACI,EAAA;AACK,EAAA;AACV;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAYgB;AACT,EAAA;AACI,EAAA;AACN,EAAA;AACE,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAGgB;AACL,EAAA;AACD,EAAA;AACV;AAagB;AACT,EAAA;AACI,EAAA;AACF,EAAA;AACF,EAAA;AACG,IAAA;AACN,IAAA;AACH,EAAA;AACM,EAAA;AACN,EAAA;AACS,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACD,IAAA;AACC,IAAA;AACF,IAAA;AACL,EAAA;AACK,EAAA;AACG,EAAA;AACA,EAAA;AACH,EAAA;AACA,EAAA;AACC,EAAA;AACD,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AAEDlC,MAAAA;AACF,MAAA;AACA,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACP,IAAA;AACD,EAAA;AACQ,EAAA;AACC,EAAA;AACV;AAYgB;AACL,EAAA;AACJ,EAAA;AACI,EAAA;AACJ,EAAA;AAEA,EAAA;AACA,EAAA;AACA,EAAA;AACoB,EAAA;AACtB,EAAA;AACE,IAAA;AACA,MAAA;AACJ,MAAA;AACD,IAAA;AACQ,IAAA;AACH,MAAA;AACJ,MAAA;AACM,MAAA;AACP,IAAA;AACQ,IAAA;AACR,EAAA;AACO,EAAA;AACD,EAAA;AACR;AAOgB;AACL,EAAA;AACN,EAAA;AACK,EAAA;AACA,EAAA;AACD,EAAA;AACD,EAAA;AACR;AAMMmC;AACO;AAEJ;AACD,EAAA;AACE,IAAA;AACR,IAAA;AAIQ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACN,MAAA;AACA,IAAA;AACH,EAAA;AACD;AAQgB;AACT,EAAA;AACF,EAAA;AACJ,EAAA;AACO,IAAA;AACC,IAAA;AACF,MAAA;AACE,IAAA;AACR,EAAA;AACS,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACH,MAAA;AACA,MAAA;AACG,IAAA;AACH,MAAA;AACA,MAAA;AACJ,IAAA;AACG,IAAA;AACK,IAAA;AACT,EAAA;AACO,EAAA;AAEF,IAAA;AACJ,IAAA;AACC,MAAA;AACC,QAAA;AACC,UAAA;AACC,YAAA;AACA,YAAA;AACD,UAAA;AACD,QAAA;AACD,MAAA;AACD,IAAA;AACQ,IAAA;AACA,IAAA;AACD,IAAA;AAEF,IAAA;AACD,IAAA;AACE,IAAA;AACF,IAAA;AACG,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACM,IAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACD,IAAA;AACK,IAAA;AACN,EAAA;AACQ,EAAA;AACT;AAYgB;AACL,EAAA;AACJ,EAAA;AACI,EAAA;AACJ,EAAA;AACE,EAAA;AACC,EAAA;AACA,IAAA;AACJ,IAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,IAAA;AACP,MAAA;AACI,QAAA;AACA,QAAA;AACC,QAAA;AACJ,QAAA;AACD,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACS,EAAA;AACV;AAOS;AACF,EAAA;AACG,EAAA;AACF,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAGS;AACE,EAAA;AACJ,EAAA;AACA5B,EAAAA;AACE,EAAA;AACR,EAAA;AACO,IAAA;AACC,IAAA;AACD,IAAA;AACC,IAAA;AACC,IAAA;AACT,EAAA;AACI,EAAA;AACG,IAAA;AACF,IAAA;AACL,EAAA;AACM,EAAA;AACC,IAAA;AACC,IAAA;AACP,EAAA;AACQ,EAAA;AACV;AAEM;AAGG;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACAP,IAAAA;AACFA,IAAAA;AACE,IAAA;AACA,IAAA;AACAoB,IAAAA;AACC,IAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEE,EAAA;AACF,EAAA;AACC,EAAA;AACA,EAAA;AACC,EAAA;AACD,EAAA;AACR;AAOgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACJ,EAAA;AACF,EAAA;AACG,EAAA;AACN,IAAA;AACM,IAAA;AACA,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AAED,MAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;Ab85IY;AACA;Ac74JN;AAGG;AACE,EAAA;AACH,EAAA;AACR;AAEMgB;AAMG;AACFC,EAAAA;AACDA,EAAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACF,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAES;AACD,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACC,IAAA;AACC,IA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A;AACDA,IAAAA;AACN,EAAA;AACM,EAAA;AACA,EAAA;AACA,EAAA;AACC,EAAA;AACD,EAAA;AACA,EAAA;AACF,EAAA;AACAZ,EAAAA;AACK,EAAA;AACH,EAAA;AAEI,EAAA;AACH,IAAA;AACF,IAAA;AACL,EAAA;AAEU,EAAA;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACE,IAAA;AACA,IAAA;AACR,EAAA;AACI,EAAA;AACM,EAAA;AACJ,EAAA;AACF,EAAA;AACI,IAAA;AACA,IAAA;AACA,IAAA;AACR,EAAA;AACI,EAAA;AAEG,EAAA;AACR;AfgtKY;AACA;AgB77KI;AACT,EAAA;AACA,EAAA;AACD,IAAA;AACE,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACN,MAAA;AACD,IAAA;AACD,EAAA;AACA,EAAA;AACO,EAAA;AACR;AAEM;AAGG;AACD,EAAA;AACR;AAGgB;AACT,EAAA;AACD,EAAA;AACC,EAAA;AACI,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACH,EAAA;AACR;AAKM;AAGU;AACP,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACF,EAAA;AACE,EAAA;AACF,EAAA;AACA,EAAA;AAEG,IAAA;AACC,IAAA;AACR,EAAA;AACS,EAAA;AACV;AAEM;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAOgB;AACP,EAAA;AACF,EAAA;AACI,EAAA;AACT,IAAA;AACM,IAAA;AACC,IAAA;AACR,EAAA;AACI,EAAA;AACH,IAAA;AACO,IAAA;AACR,EAAA;AACO,EAAA;AACR;AA+CgB;AACT,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACP,EAAA;AACK,EAAA;AACI,EAAA;AACN,EAAA;AACE,EAAA;AAIG,IAAA;AACC,IAAA;AACP,EAAA;AACG,EAAA;AACF,EAAA;AACA,EAAA;AACJ,EAAA;AACO,IAAA;AACC,IAAA;AACR,EAAA;AACO,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACI,IAAA;AACH,MAAA;AACD,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAGS;AACF,EAAA;AACF,EAAA;AACG,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACJ,EAAA;AACM,EAAA;AACR;AAMgB;AACX,EAAA;AACGW,IAAAA;AACA,IAAA;AACF,IAAA;AACAA,IAAAA;AACL,EAAA;AACM,EAAA;AACC,EAAA;AACR;AAGgB;AACT,EAAA;AACI,EAAA;AACH,EAAA;AACE,IAAA;AACD,MAAA;AACA,MAAA;AACN,MAAA;AACD,IAAA;AACI,IAAA;AACJ,EAAA;AACM,EAAA;AACR;AAGgB;AACL,EAAA;AACH,EAAA;AACE,EAAA;AACC,EAAA;AACH,EAAA;AACR;AAGS;AACF,EAAA;AACA,EAAA;AACI,EAAA;AACJ,EAAA;AACA,EAAA;AACI,EAAA;AACX;AAOgB;AACV,EAAA;AACC,EAAA;AACC,EAAA;AACR;AAwBgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACH,IAAA;AACC,MAAA;AACA,MAAA;AACF,MAAA;AACJ,MAAA;AACA,MAAA;AACD,IAAA;AACM,IAAA;AACD,IAAA;AACD,IAAA;AACA,IAAA;AACG,MAAA;AACN,MAAA;AACM,IAAA;AAED,IAAA;AACF,IAAA;AACJ,IAAA;AACO,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACK,IAAA;AACG,IAAA;AACR,EAAA;AACG,EAAA;AACC,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACL,EAAA;AACS,EAAA;AACV;AAUgB;AACT,EAAA;AACC,EAAA;AACE,IAAA;AACD,IAAA;AACR,EAAA;AACD;AhBu0KY;AACA;AiBrhLC;AAiCA;AAGA;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACS,EAAA;AACV;AAGa;AA+KJ;AACF,EAAA;AACI,EAAA;AACN,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAkEa;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAGa;AAEP;AACC,EAAA;AACE,EAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAEM;AACA;AAEO;AAMP;AAGU;AACL,EAAA;AACJ,EAAA;AACI,EAAA;AACA,EAAA;AACL,IAAA;AACJ,IAAA;AACO,MAAA;AACF,MAAA;AAAsB,QAAA;AAAmE,MAAA;AAC9F,IAAA;AACD,EAAA;AACQ,EAAA;AACT;AAGgB;AACT,EAAA;AACN,EAAA;AACO,IAAA;AACD,IAAA;AACN,EAAA;AACO,EAAA;AACR;AAMgB;AAGT,EAAA;AACA,EAAA;AACA,EAAA;AACC,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACF,IAAA;AACC,IAAA;AACD,IAAA;AACL,EAAA;AACU,EAAA;AACH,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACD,MAAA;AACA,MAAA;AACN,IAAA;AACO,IAAA;AACR,EAAA;AACU,EAAA;AACJE,EAAAA;AACN,EAAA;AACMA,IAAAA;AACC,IAAA;AACA,IAAA;AACG,IAAA;AACH,IAAA;AACD,IAAA;AACA,IAAA;AACC,IAAA;AACA,IAAA;AACF,IAAA;AACA,IAAA;AACL,EAAA;AACO,EAAA;AACR;AAGa;AAuBG;AAGL,EAAA;AACJ,EAAA;AACA,EAAA;AACN,EAAA;AACO,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACK,MAAA;AACE,MAAA;AACF,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AAEA,MAAA;AACA,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAOgB;AACV,EAAA;AACC,EAAA;AACC,EAAA;AACR;AAsBS;AACF,EAAA;AACA,EAAA;AACC,EAAA;AACE,EAAA;AACV;AAQgB;AAGL,EAAA;AACJ,EAAA;AACN,EAAA;AACO,IAAA;AACC,IAAA;AACR,EAAA;AACU,EAAA;AACA,EAAA;AACV,EAAA;AACM,EAAA;AACN,EAAA;AACO,IAAA;AACF,IAAA;AACE,IAAA;AACE,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACN,IAAA;AACC,MAAA;AACC,QAAA;AACI,QAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACH,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACD,MAAA;AACF,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGgB;AACT,EAAA;AACC,EAAA;AACR;AAGgB;AACL,EAAA;AACF,EAAA;AACE,EAAA;AACA,EAAA;AACD,EAAA;AACV;AAgBgB;AACT,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACI,EAAA;AACH,EAAA;AACR;AAEM;AAGU;AACP,EAAA;AACD,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACH,EAAA;AACD;AAgBgB;AACL,EAAA;AACJ,EAAA;AACA,EAAA;AACAC,EAAAA;AACA,EAAA;AACA1B,EAAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA0B,EAAAA;AACI,EAAA;AACD,EAAA;AACV;AAEM;AACA;AAGU;AACX,EAAA;AACE,EAAA;AACC,EAAA;AACR;AAGM;AACG,EAAA;AACA,EAAA;AACH,EAAA;AACG,EAAA;AACF,EAAA;AACG,EAAA;AACA,EAAA;AACH,EAAA;AACA,EAAA;AACI,EAAA;AACH,EAAA;AACC,EAAA;AACD,EAAA;AACP,EAAA;AACA,EAAA;AACO,EAAA;AACR;AAEM;AACF,EAAA;AACA,EAAA;AACA,EAA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AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACL,MAAA;AACD,IAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AAEQ,EAAA;AACD,IAAA;AACF,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACL,MAAA;AACD,IAAA;AACM,IAAA;AACF,IAAA;AACG,MAAA;AACD,MAAA;AACD,MAAA;AAEH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACI,MAAA;AACH,QAAA;AACI,QAAA;AACH,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACK,MAAA;AACA,MAAA;AACL,MAAA;AACD,IAAA;AACM,IAAA;AACF,IAAA;AACG,MAAA;AACD,MAAA;AACA,MAAA;AACL,MAAA;AACD,IAAA;AACA,IAAA;AACO,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AAEM,MAAA;AACA,MAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACL,MAAA;AACD,IAAA;AACM,IAAA;AACF,IAAA;AACE,MAAA;AACA,MAAA;AAAsC,MAAA;AAEtC,MAAA;AACA,MAAA;AACA,MAAA;AACL,MAAA;AACD,IAAA;AACK,IAAA;AACC,MAAA;AACN,IAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AAEgB,EAAA;AACT,IAAA;AACA,IAAA;AACF,IAAA;AACG,MAAA;AACA,MAAA;AACF,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACM,MAAA;AACA,MAAA;AACFxB,MAAAA;AACC,MAAA;AACCa,MAAAA;AACA,MAAA;AACD,MAAA;AACC6B,MAAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACL,MAAA;AACD,IAAA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F,IAAA;AACL,EAAA;AACM,EAAA;AACF,EAAA;AACH,IAAA;AACK,MAAA;AACE,MAAA;AACF,MAAA;AACL,IAAA;AACD,EAAA;AACK,EAAA;AACC,EAAA;AACA,EAAA;AAEAqB,EAAAA;AACN,EAAA;AACC,IAAA;AACK,MAAA;AACE,MAAA;AACNA,MAAAA;AACD,IAAA;AACD,EAAA;AACM,EAAA;AACC,EAAA;AACA,IAAA;AACE,IAAA;AACR,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACO,IAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACA,IAAA;AACD,EAAA;AACD;AAES;AACF,EAAA;AACC,EAAA;AACR;AAGa;AACA;AACA;AACF,EAAA;AACH,EAAA;AACD,EAAA;AACE,EAAA;AACA,EAAA;AACH,EAAA;AACN;AAGgB;AACX,EAAA;AACM,EAAA;AACF,EAAA;AACF,EAAA;AACC,EAAA;AACR;AAmBa;AACN,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACP;AAYa;AACN,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACP;AAGa;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACR;AA8Ba;AAGG;AACT,EAAA;AACD,EAAA;AACC,EAAA;AACC,EAAA;AACE,IAAA;AACD,IAAA;AACP,IAAA;AACO,MAAA;AACA,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACC,IAAA;AACF,IAAA;AACD,EAAA;AACD;AlB0vSY;AACA;AmBnlUN;AAGQ;AACH,EAAA;AACX;AAEc;AAER;AACC,EAAA;AACC,EAAA;AACA,IAAA;AACN,IAAA;AACA,IAAA;AACD,EAAA;AACA,EAAA;AACD;AAEM;AACC,EAAA;AACG,EAAA;AACC,EAAA;AACV,EAAA;AACD;AAEM;AACE,EAAA;AACA,EAAA;AACA,IAAA;AACN,IAAA;AACM,MAAA;AACC,MAAA;AACN,MAAA;AACD,IAAA;AACA,IAAA;AACD,EAAA;AACA,EAAA;AACD;AAEM;AACI,EAAA;AACA,EAAA;AACC,EAAA;AACV,EAAA;AACU,EAAA;AACX;AAEM;AACD,EAAA;AACA,EAAA;AACA,EAAA;AACL;AAEc;AACb,EAAA;AACD;AAEM;AACC,EAAA;AACE,EAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACU,EAAA;AACV,EAAA;AACA,EAAA;AACU,EAAA;AACV,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAEM;AACC,EAAA;AACN,EAAA;AACO,EAAA;AACR;AAEc;AACR,EAAA;AACN;AAQgB;AACT,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACCgC,IAAAA;AACN,IAAA;AACKA,IAAAA;AACEA,IAAAA;AACR,EAAA;AACM,EAAA;AACA,EAAA;AACA,IAAA;AACL,IAAA;AACO,MAAA;AACF,MAAA;AACL,IAAA;AACO,IAAA;AACR,EAAA;AAEM,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AACL,MAAA;AACC,QAAA;AACA,QAAA;AACI,QAAA;AACH,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AAGA,UAAA;AAGA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACG,QAAA;AACL,MAAA;AACD,IAAA;AACM,IAAA;AACC,IAAA;AACR,EAAA;AAEO,EAAA;AACN,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACK,MAAA;AACN,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,M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;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACI,QAAA;AACJ,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AAEA,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACD,QAAA;AACH,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACI,QAAA;AACJ,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AAEA,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACI,QAAA;AACJ,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AAEA,MAAA;AACC,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACD,MAAA;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACP,IAAA;AACA,IAAA;AACI,MAAA;AACC,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACM,MAAA;AACL,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACD;AnB6iUY;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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/vector/index.cjs","sourcesContent":[null,"import type { CanvasGradientDef } from '../canvas/gradient.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/** A fill or stroke: any CSS colour, a gradient, or `null` / `'none'` for no paint. */\nexport type VectorPaint = string | 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/** 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}\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}\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/** 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}\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).\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\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];\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) and `repeater`.\n */\nexport type VectorEffectType = 'trim' | 'roundCorners' | 'puckerBloat' | 'twist' | 'zigzag' | 'wiggle' | 'offset' | 'merge' | 'repeater';\nexport const VECTOR_EFFECT_TYPES: readonly VectorEffectType[] = ['trim', 'roundCorners', 'puckerBloat', 'twist', 'zigzag', 'wiggle', 'offset', 'merge', 'repeater'];\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}\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 * 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 * 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","/**\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 { 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 type {\n\tVec2,\n\tVectorBooleanOperation,\n\tVectorEffect,\n\tVectorEffectParamsMap,\n\tVectorEffectType,\n\tVectorFillRule,\n\tVectorGroupNode,\n\tVectorNode,\n\tVectorPathNode\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};\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};\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};\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\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 '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). */\nfunction childShapes(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 = childShapes(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/**\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\tlet base: VectorAppearanceShape[];\n\tif (node.type === 'path') base = [{ source: node, path: subpathsToBezier(node.subpaths), opacity: 1, order: [] }];\n\telse if (node.liveBoolean) base = liveBooleanShapes(node);\n\telse base = node.children.flatMap((c, i) => childShapes(c, i));\n\tconst out = applyVectorEffects(base, 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...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};\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 {\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): 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 mirror = m[0] * m[3] - m[1] * m[2] < 0 ? -1 : 1;\n\treturn effects.map((e) => {\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`. 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);\n\t\t\tif (want.has(n.id)) {\n\t\t\t\tfor (const id of live) out.add(id);\n\t\t\t\tif (own) 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 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 { expandVectorCorners, setSubpathCorners } from './corners.js';\nimport { hitTestVectorStroke, vectorStrokeOutlinePath } from './stroke.js';\nimport { mirrorWidthProfile } from './width.js';\nimport { expandVectorAppearance, hasOwnVectorAppearance, transformVectorEffects, vectorAppearanceOutline } from './appearance.js';\nimport type {\n\tVectorAnchor,\n\tVectorAnchorType,\n\tVectorCornerKind,\n\tVectorBounds,\n\tVectorDocument,\n\tVectorGroupNode,\n\tVectorNode,\n\tVectorPaint,\n\tVectorPathNode,\n\tVectorSubpath,\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.\n */\nexport function vectorNodeBounds(node: VectorNode, parent: AffineMatrix = IDENTITY_MATRIX): VectorBounds | null {\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.\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\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\tconst point: Point = [p.x, p.y];\n\tif (hasPaint(node.fill) && pointInBezierPath(path, point, node.fillRule ?? 'nonzero')) return true;\n\tif (hasPaint(node.stroke)) return hitTestVectorStroke(path, p, node, tolerance);\n\treturn distanceToBezierPath(path, point) <= tolerance;\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\nexport const IDENTITY_MATRIX: AffineMatrix = [1, 0, 0, 1, 0, 0];\n\n/** `a` then `b` (apply `a` first). */\nexport function multiplyMatrices(a: AffineMatrix, b: AffineMatrix): AffineMatrix {\n\tconst [a0, a1, a2, a3, a4, a5] = a;\n\tconst [b0, b1, b2, b3, b4, b5] = b;\n\treturn [b0 * a0 + b2 * a1, b1 * a0 + b3 * a1, b0 * a2 + b2 * a3, b1 * a2 + b3 * a3, b0 * a4 + b2 * a5 + b4, b1 * a4 + b3 * a5 + b5];\n}\n\nexport const translateMatrix = (dx: number, dy: number): AffineMatrix => [1, 0, 0, 1, dx, dy];\n\nexport function scaleMatrix(sx: number, sy: number, origin: Vec2 = { x: 0, y: 0 }): AffineMatrix {\n\treturn [sx, 0, 0, sy, origin.x - sx * origin.x, origin.y - sy * origin.y];\n}\n\n/** Rotation by `degrees` clockwise (y points down) around `origin`. */\nexport function rotateMatrix(degrees: number, origin: Vec2 = { x: 0, y: 0 }): AffineMatrix {\n\tconst r = (degrees * Math.PI) / 180;\n\tconst cos = Math.cos(r);\n\tconst sin = Math.sin(r);\n\treturn [cos, sin, -sin, cos, origin.x - cos * origin.x + sin * origin.y, origin.y - sin * origin.x - cos * origin.y];\n}\n\nexport function applyMatrix(m: AffineMatrix, p: Vec2): Vec2 {\n\treturn { x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] };\n}\n\nexport function isIdentityMatrix(m: AffineMatrix, eps = 1e-12): boolean {\n\treturn Math.abs(m[0] - 1) < eps && Math.abs(m[1]) < eps && Math.abs(m[2]) < eps && Math.abs(m[3] - 1) < eps && Math.abs(m[4]) < eps && Math.abs(m[5]) < eps;\n}\n\n/** The inverse matrix (the identity for a singular one). */\nexport function invertMatrix(m: AffineMatrix): AffineMatrix {\n\tconst [a, b, c, d, e, f] = m;\n\tconst det = a * d - b * c;\n\tif (!det || !Number.isFinite(det)) return IDENTITY_MATRIX;\n\treturn [d / det, -b / det, -c / det, a / det, (c * f - d * e) / det, (b * e - a * f) / det];\n}\n\n/** Applies only the linear part (for directions and offsets). */\nexport function applyMatrixLinear(m: AffineMatrix, v: Vec2): Vec2 {\n\treturn { x: m[0] * v.x + m[2] * v.y, y: m[1] * v.x + m[3] * v.y };\n}\n\n/**\n * The rotation of a matrix in degrees, clockwise (y down), in (−180, 180]. For a mirrored\n * matrix it is the rotation left after undoing a horizontal flip, so a flipped rotated shape\n * keeps its angle.\n */\nexport function matrixRotation(m: AffineMatrix): number {\n\tconst [a, b, c, d] = m;\n\tconst mirrored = a * d - b * c < 0;\n\tconst deg = ((mirrored ? Math.atan2(-b - c, d - a) : Math.atan2(b - c, a + d)) * 180) / Math.PI;\n\treturn Math.abs(deg) < 1e-9 ? 0 : deg;\n}\n\n/**\n * Splits `m` into `residual` then `rigid` (`m = residual × rigid` in apply order): `rigid` is\n * the nearest rotation plus the translation, `residual` the linear rest (scale, skew,\n * reflection). An unrotated `m` gives an identity `rigid` and everything in `residual`.\n */\nexport function decomposeRigid(m: AffineMatrix): { rigid: AffineMatrix; residual: AffineMatrix } {\n\tconst [a, b, c, d, e, f] = m;\n\tconst deg = matrixRotation(m);\n\tif (deg === 0) return { rigid: IDENTITY_MATRIX, residual: m };\n\tconst r = (deg * Math.PI) / 180;\n\tconst cos = Math.cos(r);\n\tconst sin = Math.sin(r);\n\treturn {\n\t\trigid: [cos, sin, -sin, cos, e, f],\n\t\tresidual: [cos * a + sin * b, -sin * a + cos * b, cos * c + sin * d, -sin * c + cos * d, 0, 0]\n\t};\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) } 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 mirrored = m[0] * m[3] - m[1] * m[2] < 0 && path.strokeWidthProfile?.length;\n\treturn {\n\t\t...path,\n\t\t...(mirrored ? { strokeWidthProfile: mirrorWidthProfile(path.strokeWidthProfile!) } : {}),\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 * 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 and `transform`s. Text, images, clip paths, masks, filters and `<use>` are\n * 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 */\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 { hasOwnVectorAppearance, sanitizeVectorEffects, transformVectorEffects, vectorAppearanceNodes } from './appearance.js';\nimport { gradientToSvgDef, type CanvasGradientDef, type GradientStop, type SvgGradientDef } from '../canvas/gradient.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\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 VectorFillRule,\n\ttype VectorGroupNode,\n\ttype VectorLineCap,\n\ttype VectorLineJoin,\n\ttype VectorWidthPoint,\n\ttype VectorNode,\n\ttype VectorPaint,\n\ttype VectorBounds,\n\ttype VectorPathNode,\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}\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/** A paint as an SVG attribute value plus the gradient def it needs (if any). */\nexport function vectorPaintToSvg(paint: VectorPaint | undefined, gradientId: string): { value: string; gradient: SvgGradientDef | null } {\n\tif (!hasPaint(paint)) return { value: 'none', gradient: null };\n\tif (typeof paint === 'string') return { value: paint, gradient: null };\n\treturn { value: `url(#${gradientId})`, gradient: gradientToSvgDef(paint as CanvasGradientDef, gradientId) };\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/** 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): 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}>${stops}</${tag}>`;\n}\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\treturn out;\n\t};\n\n\tconst emit = (node: VectorNode, depth: number) => {\n\t\tif (node.visible === false && !options.includeHidden) return;\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, ...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\tconst fill = vectorPaintToSvg(node.fill, `${prefix}fill-${node.id}`);\n\t\tconst stroke = vectorPaintToSvg(node.stroke, `${prefix}stroke-${node.id}`);\n\t\tif (fill.gradient) defs.push(gradientMarkup(fill.gradient));\n\t\tif (stroke.gradient) defs.push(gradientMarkup(stroke.gradient));\n\t\tconst d = vectorPathData(node, precision);\n\t\tconst corners = vectorCornersAttribute(node.subpaths, precision);\n\t\tconst live = corners ? ` data-vector-d=\"${bezierPathToSvg(subpathsToBezier(node.subpaths, { corners: false }), { precision })}\" data-vector-corners=\"${corners}\"` : '';\n\t\tconst fillAttrs = ` fill=\"${escapeAttr(fill.value)}\"${node.fillRule === 'evenodd' ? ` fill-rule=\"evenodd\"` : ''}`;\n\t\tconst lineAttrs = (width: number) => {\n\t\t\tlet out = ` stroke=\"${escapeAttr(stroke.value)}\" stroke-width=\"${num(width)}\"`;\n\t\t\tif (node.strokeLinecap && node.strokeLinecap !== 'butt') out += ` stroke-linecap=\"${node.strokeLinecap}\"`;\n\t\t\tif (node.strokeLinejoin && node.strokeLinejoin !== 'miter') out += ` stroke-linejoin=\"${node.strokeLinejoin}\"`;\n\t\t\tif (node.strokeDasharray?.length) out += ` stroke-dasharray=\"${node.strokeDasharray.map(num).join(' ')}\"`;\n\t\t\treturn out;\n\t\t};\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\tif (options.strokes === 'outline') {\n\t\t\tconst area = bezierPathToSvg(vectorStrokeOutlinePath(subpathsToBezier(node.subpaths), node), { precision });\n\t\t\tlines.push(`${indent(depth + 1)}<path data-vector-part=\"stroke-outline\" d=\"${area}\" fill=\"${escapeAttr(stroke.value)}\"/>`);\n\t\t} else {\n\t\t\tlet clip = '';\n\t\t\tif (parts.clip) {\n\t\t\t\tconst rule = node.fillRule === 'evenodd' ? 'evenodd' : 'nonzero';\n\t\t\t\tconst id = escapeAttr(parts.clip.id);\n\t\t\t\tif (parts.clip.kind === 'clip') {\n\t\t\t\t\tdefs.push(`<clipPath id=\"${id}\"><path d=\"${d}\" clip-rule=\"${rule}\"/></clipPath>`);\n\t\t\t\t\tclip = ` clip-path=\"url(#${id})\"`;\n\t\t\t\t} else {\n\t\t\t\t\tconst b = parts.clip.bounds;\n\t\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\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\t\tclip = ` mask=\"url(#${id})\"`;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (parts.outline !== undefined) lines.push(`${indent(depth + 1)}<path data-vector-part=\"stroke-outline\" d=\"${parts.outline}\" fill=\"${escapeAttr(stroke.value)}\"${clip}/>`);\n\t\t\telse lines.push(`${indent(depth + 1)}<path data-vector-part=\"stroke\" d=\"${parts.d}\" fill=\"none\"${lineAttrs(parts.width)}${clip}/>`);\n\t\t\tfor (const h of parts.heads) lines.push(`${indent(depth + 1)}<path data-vector-part=\"head\" d=\"${h}\" fill=\"${escapeAttr(stroke.value)}\"/>`);\n\t\t}\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}\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}\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};\n\nfunction readStyle(el: Element, parent: InheritedStyle): InheritedStyle {\n\tconst out = { ...parent };\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): GradientStop[] {\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\nfunction collectGradients(el: Element, raw: Map<string, Element>) {\n\tif ((el.name === 'linearGradient' || el.name === 'radialGradient') && el.attrs.id) raw.set(el.attrs.id, el);\n\tfor (const c of el.children) collectGradients(c, raw);\n}\n\nfunction resolveGradient(id: string, raw: Map<string, Element>, seen = new Set<string>()): CanvasGradientDef | null {\n\tconst el = raw.get(id);\n\tif (!el || seen.has(id)) return null;\n\tseen.add(id);\n\tlet stops = readStops(el);\n\tconst href = el.attrs.href ?? el.attrs['xlink:href'];\n\tif (!stops.length && href?.startsWith('#')) stops = resolveGradient(href.slice(1), raw, seen)?.stops ?? [];\n\tif (!stops.length) return null;\n\tif (el.name === 'radialGradient') {\n\t\treturn { type: 'radial', cx: fraction(el.attrs.cx, 0.5), cy: fraction(el.attrs.cy, 0.5), r: fraction(el.attrs.r, 0.5), stops };\n\t}\n\tconst x1 = fraction(el.attrs.x1, 0);\n\tconst y1 = fraction(el.attrs.y1, 0);\n\tconst x2 = fraction(el.attrs.x2, 1);\n\tconst y2 = fraction(el.attrs.y2, 0);\n\t// CSS angle: 0° points up, clockwise.\n\tconst angle = Math.round(((Math.atan2(x2 - x1, -(y2 - y1)) * 180) / Math.PI + 360) % 360);\n\treturn { type: 'linear', angle, stops };\n}\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 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 (hasPaint(next.stroke) && strokeScale > 0 && Math.abs(strokeScale - 1) > 1e-9) next.strokeWidth = (next.strokeWidth ?? 1) * strokeScale;\n\t\treturn next;\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\">): 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 m = multiplyMatrices(parseSvgTransform(child.attrs.transform), matrix);\n\t\t\tconst s = readStyle(child, style);\n\t\t\tconst opacity = child.attrs.opacity !== undefined ? parseFloat(child.attrs.opacity) : undefined;\n\t\t\tconst hidden = child.attrs.display === 'none' || child.attrs.visibility === 'hidden';\n\t\t\tconst meta = {\n\t\t\t\t...(child.attrs['inkscape:label'] || child.attrs['data-name'] ? { name: child.attrs['inkscape:label'] ?? child.attrs['data-name'] } : {}),\n\t\t\t\t...(opacity !== undefined && Number.isFinite(opacity) && opacity < 1 ? { opacity } : {}),\n\t\t\t\t...(hidden ? { visible: false } : {})\n\t\t\t};\n\t\t\t// A blend's in-betweens are regenerated from its keys.\n\t\t\tif (child.attrs['data-vector-blend-step'] !== undefined) continue;\n\t\t\tconst operation = child.attrs['data-vector-boolean-operation'];\n\t\t\tif (BOOLEAN_OPERATIONS.includes(operation as VectorBooleanOperation)) (meta as { booleanOperation?: VectorBooleanOperation }).booleanOperation = operation as VectorBooleanOperation;\n\t\t\tif (child.name === 'g' && child.attrs['data-vector-live'] !== undefined) {\n\t\t\t\tconst node = liveNode(child, s, m, meta);\n\t\t\t\tif (node) out.push(node);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (child.name === 'g' && child.attrs['data-vector-path'] !== undefined) {\n\t\t\t\tconst node = mergedPath(child, s, m, meta);\n\t\t\t\tif (node) out.push(node);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (child.name === 'g' || child.name === 'svg' || child.name === 'a' || child.name === 'switch') {\n\t\t\t\tconst children = visit(child, s, m);\n\t\t\t\tif (!children.length) continue;\n\t\t\t\tconst group: VectorGroupNode = { id: idFor(child), type: 'group', ...meta, children };\n\t\t\t\tconst blend = child.name === 'g' ? blendFromAttribute(child.attrs['data-vector-blend'], m) : null;\n\t\t\t\tif (blend) group.blend = blend;\n\t\t\t\tout.push(group);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst subpaths = shapeOf(child);\n\t\t\tif (!subpaths || !subpaths.length) continue;\n\t\t\tconst node = placed(\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\tm\n\t\t\t);\n\t\t\tout.push(node);\n\t\t}\n\t\treturn out;\n\t};\n\n\tconst nodes = visit(root, readStyle(root, { fill: '#000', stroke: 'none', strokeWidth: '' }), rootMatrix);\n\treturn { width, height, nodes };\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 kind 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 type { CanvasGradientDef, GradientStop } from '../canvas/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 { VectorAnchor, VectorBounds, 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\nfunction sortedStops(g: CanvasGradientDef): GradientStop[] {\n\treturn g.stops.slice().sort((x, y) => x.offset - y.offset);\n}\n\n/** What a gradient paints at `offset`: colour mixed in OKLab and opacity. */\nfunction sampleGradient(stops: readonly GradientStop[], offset: number): { color: string; opacity: number } {\n\tif (!stops.length) return { color: 'transparent', opacity: 1 };\n\tconst first = stops[0];\n\tconst last = stops[stops.length - 1];\n\tif (offset <= first.offset) return { color: first.color, opacity: first.opacity ?? 1 };\n\tif (offset >= last.offset) return { color: last.color, opacity: last.opacity ?? 1 };\n\tlet i = 0;\n\twhile (i + 1 < stops.length && stops[i + 1].offset < offset) i++;\n\tconst x = stops[i];\n\tconst y = stops[i + 1];\n\tconst u = y.offset - x.offset > 1e-12 ? (offset - x.offset) / (y.offset - x.offset) : 1;\n\treturn { color: interpolateVectorColor(x.color, y.color, u), opacity: lerpN(x.opacity ?? 1, y.opacity ?? 1, u) };\n}\n\n/** A gradient's stops resampled at `offsets`, each painting what the gradient paints there. */\nexport function resampleGradientStops(g: CanvasGradientDef, offsets: readonly number[]): GradientStop[] {\n\tconst stops = sortedStops(g);\n\treturn offsets.map((offset) => {\n\t\tconst s = sampleGradient(stops, offset);\n\t\treturn s.opacity < 1 ? { offset, color: s.color, opacity: s.opacity } : { 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\n/** Two gradients of one kind: stops at every offset either has, mixed. Null for different kinds. */\nfunction lerpGradient(a: CanvasGradientDef, b: CanvasGradientDef, t: number): CanvasGradientDef | null {\n\tif (a.type !== b.type) return null;\n\tlet stopsA = sortedStops(a);\n\tlet stopsB = sortedStops(b);\n\tif (stopsA.length !== stopsB.length) {\n\t\tconst offs = [...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: GradientStop = { offset: lerpN(x.offset, y.offset, t), color: interpolateVectorColor(x.color, y.color, t) };\n\t\tif (opacity < 1) stop.opacity = opacity;\n\t\treturn stop;\n\t});\n\tif (a.type === 'linear' && b.type === 'linear') return { type: 'linear', angle: lerpAngle(a.angle ?? 90, b.angle ?? 90, t), stops };\n\tconst ra = a as Extract<CanvasGradientDef, { type: 'radial' }>;\n\tconst rb = b as Extract<CanvasGradientDef, { type: 'radial' }>;\n\treturn { type: 'radial', cx: lerpN(ra.cx ?? 0.5, rb.cx ?? 0.5, t), cy: lerpN(ra.cy ?? 0.5, rb.cy ?? 0.5, t), r: lerpN(ra.r ?? 0.5, rb.r ?? 0.5, t), stops };\n}\n\n/** A gradient shaped like `like` painting one colour (or nothing). */\nfunction solidLike(color: string | null, like: CanvasGradientDef): CanvasGradientDef {\n\treturn { ...like, stops: like.stops.map((s) => (color === null ? { offset: s.offset, color: s.color, opacity: 0 } : { offset: s.offset, color })) } as CanvasGradientDef;\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 && typeof a === 'object' && a ? a : null;\n\tconst gb = pb && typeof b === 'object' && 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","// Portions ported from vectorcraft-main/crates/doc/src/blend.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n/**\n * Live blend groups: a group whose `blend` is set keeps its key shapes as children and\n * regenerates the in-betweens (`blendStep` children, locked) whenever a key or the options\n * change. Steps follow the straight lines between the keys' centres, or a replaced spine.\n *\n * - **Spacing** (`vectorBlendStepCount`): a number of steps per pair of keys, a fixed distance\n * along the spine, or smooth colour — one step per two levels of the largest colour channel\n * difference, by distance when the colours match.\n * - **Spine**: with a replaced spine the keys sit along it, spread evenly by arc length; each\n * step is moved by how far the spine bends away from the chord between its keys, so a step\n * lands on the spine and the keys stay where they are. Orient to spine also turns the steps\n * by how much the spine's direction differs from the keys' (keys keep their orientation);\n * without a replaced spine the steps run straight and keep their orientation.\n */\nimport type { Point } from '../canvas/types.js';\nimport { hexToRgba } from '../tokens/color.js';\nimport { segmentPoint, segmentTangent } from '../canvas/pathops/bezier.js';\nimport { createVectorMorph, type VectorMorph } from './blend.js';\nimport {\n\tcollectPathNodes,\n\tIDENTITY_MATRIX,\n\trotateMatrix,\n\tsubpathSegment,\n\tsubpathSegmentCount,\n\ttransformVectorNode,\n\ttranslateMatrix,\n\tmultiplyMatrices,\n\tnodeMatrix,\n\tvectorNodeBounds,\n\ttype AffineMatrix\n} from './geometry.js';\nimport type { VectorBlend, VectorBlendOptions, VectorGroupNode, VectorNode, VectorPaint, VectorPathNode, VectorSubpath } from './types.js';\n\nexport const DEFAULT_VECTOR_BLEND_STEPS = 5;\nexport const DEFAULT_VECTOR_BLEND_DISTANCE = 20;\n/** Most steps between two keys. */\nexport const VECTOR_BLEND_MAX_STEPS = 1000;\n/** Most steps smooth colour makes between two keys. */\nexport const VECTOR_BLEND_MAX_SMOOTH_STEPS = 256;\n\n/** Whether a node is a live blend group. */\nexport function isVectorBlendGroup(node: VectorNode | null | undefined): node is VectorGroupNode & { blend: VectorBlend } {\n\treturn !!node && node.type === 'group' && !!node.blend;\n}\n\n/** The keys of a blend group (its children that are not generated steps), in stacking order. */\nexport function vectorBlendKeys(group: VectorGroupNode): VectorNode[] {\n\treturn group.children.filter((c) => !c.blendStep);\n}\n\n/** The full options of a blend, defaults filled in. */\nexport function resolveVectorBlendOptions(options: VectorBlendOptions | null | undefined): Required<VectorBlendOptions> {\n\treturn {\n\t\tspacing: options?.spacing ?? 'steps',\n\t\tsteps: options?.steps ?? DEFAULT_VECTOR_BLEND_STEPS,\n\t\tdistance: options?.distance ?? DEFAULT_VECTOR_BLEND_DISTANCE,\n\t\torientToSpine: options?.orientToSpine ?? false,\n\t\tlinear: options?.linear ?? false\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Step counts\n// ---------------------------------------------------------------------------\n\nfunction paintColors(p: VectorPaint | undefined): string[] {\n\tif (typeof p === 'string') return [p];\n\tif (p && typeof p === 'object') return p.stops.map((s) => s.color);\n\treturn [];\n}\n\n/** Largest channel difference (0–1) between the colours of two shapes' fills and strokes. */\nexport function vectorBlendColorDistance(a: VectorNode, b: VectorNode): number {\n\tconst pa = collectPathNodes(a)[0];\n\tconst pb = collectPathNodes(b)[0];\n\tif (!pa || !pb) return 0;\n\tlet d = 0;\n\tfor (const key of ['fill', 'stroke'] as const) {\n\t\tconst ca = paintColors(pa[key]);\n\t\tconst cb = paintColors(pb[key]);\n\t\tca.forEach((c, i) => {\n\t\t\tconst other = cb[i] ?? cb[0];\n\t\t\tif (other === undefined) return;\n\t\t\tconst x = hexToRgba(c);\n\t\t\tconst y = hexToRgba(other);\n\t\t\tif (!x || !y) return;\n\t\t\td = Math.max(d, Math.abs(x.r - y.r), Math.abs(x.g - y.g), Math.abs(x.b - y.b), Math.abs(x.a - y.a));\n\t\t});\n\t}\n\treturn d;\n}\n\nconst centreOf = (n: VectorNode): Point => {\n\tconst b = vectorNodeBounds(n);\n\treturn b ? [b.x + b.width / 2, b.y + b.height / 2] : [0, 0];\n};\n\n/** Number of in-betweens between two keys `length` apart along the spine. */\nexport function vectorBlendStepCount(a: VectorNode, b: VectorNode, options: VectorBlendOptions | null | undefined, length: number): number {\n\tconst o = resolveVectorBlendOptions(options);\n\tif (o.spacing === 'distance') {\n\t\tconst d = Math.max(0.01, o.distance);\n\t\treturn Math.min(VECTOR_BLEND_MAX_STEPS, Math.max(0, Math.round(length / d) - 1));\n\t}\n\tif (o.spacing === 'smoothColor') {\n\t\tconst cd = vectorBlendColorDistance(a, b);\n\t\tif (cd > 1 / 255) return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil((cd * 255) / 2)));\n\t\t// Same colours: base the count on how far the shapes are apart.\n\t\tconst ba = vectorNodeBounds(a);\n\t\tconst bb = vectorNodeBounds(b);\n\t\tconst dist =\n\t\t\tba && bb\n\t\t\t\t? Math.max(Math.abs(ba.x - bb.x), Math.abs(ba.y - bb.y), Math.abs(ba.x + ba.width - bb.x - bb.width), Math.abs(ba.y + ba.height - bb.y - bb.height))\n\t\t\t\t: length;\n\t\treturn Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(dist / 2)));\n\t}\n\treturn Math.min(VECTOR_BLEND_MAX_STEPS, Math.max(1, Math.round(Number.isFinite(o.steps) ? o.steps : DEFAULT_VECTOR_BLEND_STEPS)));\n}\n\n// ---------------------------------------------------------------------------\n// Spine\n// ---------------------------------------------------------------------------\n\n/** A subpath flattened to a polyline with cumulative arc lengths and tangent angles. */\nexport interface VectorBlendRail {\n\tpts: Point[];\n\tcum: number[];\n\tangles: number[];\n\ttotal: number;\n\tclosed: boolean;\n}\n\n/** Flattens a spine subpath (32 samples per curve). Null with fewer than two distinct points. */\nexport function vectorBlendRail(sp: VectorSubpath): VectorBlendRail | null {\n\tconst pts: Point[] = [];\n\tconst angles: number[] = [];\n\tconst push = (p: Point, tangent: Point) => {\n\t\tconst last = pts[pts.length - 1];\n\t\tif (last && Math.hypot(p[0] - last[0], p[1] - last[1]) < 1e-9) return;\n\t\tpts.push(p);\n\t\tangles.push(Math.atan2(tangent[1], tangent[0]));\n\t};\n\tconst count = subpathSegmentCount(sp);\n\tfor (let i = 0; i < count; i++) {\n\t\tconst seg = subpathSegment(sp, i);\n\t\tconst samples = seg.length === 2 ? 1 : 32;\n\t\tif (i === 0) push(seg[0], segmentTangent(seg, 0));\n\t\tfor (let k = 1; k <= samples; k++) push(segmentPoint(seg, k / samples), segmentTangent(seg, k / samples));\n\t}\n\tif (pts.length < 2) return null;\n\tconst cum = [0];\n\tfor (let i = 1; i < pts.length; i++) cum.push(cum[i - 1] + Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]));\n\treturn { pts, cum, angles, total: cum[cum.length - 1], closed: sp.closed };\n}\n\n/** Point and direction (radians) at arc length `s` (clamped; a closed rail wraps). */\nexport function vectorBlendRailAt(rail: VectorBlendRail, s: number): { point: Point; angle: number } {\n\tlet len = Number.isFinite(s) ? s : 0;\n\tif (rail.closed && rail.total > 0) len = ((len % rail.total) + rail.total) % rail.total;\n\tlen = Math.min(rail.total, Math.max(0, len));\n\tlet lo = 0;\n\tlet hi = rail.cum.length - 1;\n\twhile (hi - lo > 1) {\n\t\tconst mid = (lo + hi) >> 1;\n\t\tif (rail.cum[mid] <= len) lo = mid;\n\t\telse hi = mid;\n\t}\n\tconst span = rail.cum[hi] - rail.cum[lo];\n\tconst u = span > 1e-12 ? (len - rail.cum[lo]) / span : 0;\n\tconst a = rail.pts[lo];\n\tconst b = rail.pts[hi];\n\treturn { point: [a[0] + (b[0] - a[0]) * u, a[1] + (b[1] - a[1]) * u], angle: Math.atan2(b[1] - a[1], b[0] - a[0]) };\n}\n\n/**\n * The spine of a blend in the group's space and where along it each key sits: the replaced\n * spine (keys spread evenly by arc length) or the straight lines between the keys' centres.\n */\nexport function vectorBlendSpine(group: VectorGroupNode): { spine: VectorSubpath; rail: VectorBlendRail; stops: number[]; replaced: boolean } | null {\n\tconst keys = vectorBlendKeys(group);\n\tif (keys.length < 2) return null;\n\tconst stored = group.blend?.spine;\n\tif (stored && stored.anchors.length >= 2) {\n\t\tconst rail = vectorBlendRail(stored);\n\t\tif (rail) {\n\t\t\tconst end = rail.closed ? rail.total * (1 - 1 / keys.length) : rail.total;\n\t\t\treturn { spine: stored, rail, stops: keys.map((_, i) => (end * i) / (keys.length - 1)), replaced: true };\n\t\t}\n\t}\n\tconst centres = keys.map(centreOf);\n\tconst spine: VectorSubpath = { closed: false, anchors: centres.map((c) => ({ x: c[0], y: c[1] })) };\n\tconst stops = [0];\n\tfor (let i = 1; i < centres.length; i++) stops.push(stops[i - 1] + Math.hypot(centres[i][0] - centres[i - 1][0], centres[i][1] - centres[i - 1][1]));\n\tconst rail = vectorBlendRail(spine) ?? { pts: centres, cum: stops, angles: centres.map(() => 0), total: stops[stops.length - 1], closed: false };\n\treturn { spine, rail, stops, replaced: false };\n}\n\n/** Where key `i` of a blend with spine `spine` should sit: its stop on the spine. */\nexport function vectorBlendKeyTargets(group: VectorGroupNode): Point[] {\n\tconst s = vectorBlendSpine(group);\n\treturn s ? s.stops.map((stop) => vectorBlendRailAt(s.rail, stop).point) : [];\n}\n\n// ---------------------------------------------------------------------------\n// Generating steps\n// ---------------------------------------------------------------------------\n\nconst morphCache = new WeakMap<VectorNode, WeakMap<VectorNode, Map<string, VectorMorph>>>();\n\nfunction morphFor(a: VectorNode, b: VectorNode, blend: VectorBlend): VectorMorph {\n\tconst starts = [blend.starts?.[a.id] ?? null, blend.starts?.[b.id] ?? null] as const;\n\tconst key = JSON.stringify([!!blend.linear, starts]);\n\tlet inner = morphCache.get(a);\n\tif (!inner) morphCache.set(a, (inner = new WeakMap()));\n\tlet byKey = inner.get(b);\n\tif (!byKey) inner.set(b, (byKey = new Map()));\n\tlet morph = byKey.get(key);\n\tif (!morph) byKey.set(key, (morph = createVectorMorph(a, b, { linear: blend.linear, starts })));\n\treturn morph;\n}\n\nconst wrapAngle = (a: number) => Math.atan2(Math.sin(a), Math.cos(a));\n\n/** A step marked as generated: locked, flagged, ids derived from `id`. */\nfunction asStep(node: VectorNode, id: string): VectorNode {\n\tconst rename = (n: VectorNode, nid: string): VectorNode =>\n\t\tn.type === 'group' ? { ...n, id: nid, children: n.children.map((c, i) => rename(c, `${nid}.${i}`)) } : { ...n, id: nid };\n\treturn { ...rename(node, id), blendStep: true, locked: true };\n}\n\n/**\n * A blend group's children with fresh steps: each key followed by the in-betweens to the next,\n * bottom to top. Groups with fewer than two keys keep just their keys.\n */\nexport function generateVectorBlendChildren(group: VectorGroupNode): VectorNode[] {\n\tconst keys = vectorBlendKeys(group);\n\tconst blend = group.blend;\n\tif (!blend || keys.length < 2) return keys;\n\tconst spine = vectorBlendSpine(group);\n\tif (!spine) return keys;\n\tconst out: VectorNode[] = [];\n\tlet counter = 0;\n\tkeys.forEach((a, i) => {\n\t\tout.push(a);\n\t\tconst b = keys[i + 1];\n\t\tif (!b) return;\n\t\tconst s0 = spine.stops[i];\n\t\tconst s1 = spine.stops[i + 1];\n\t\tconst n = vectorBlendStepCount(a, b, blend, Math.abs(s1 - s0));\n\t\tconst morph = morphFor(a, b, blend);\n\t\tconst p0 = vectorBlendRailAt(spine.rail, s0);\n\t\tconst p1 = vectorBlendRailAt(spine.rail, s1);\n\t\tfor (let j = 1; j <= n; j++) {\n\t\t\tconst t = j / (n + 1);\n\t\t\tlet step = morph.at(t);\n\t\t\tif (spine.replaced) {\n\t\t\t\tconst p = vectorBlendRailAt(spine.rail, s0 + (s1 - s0) * t);\n\t\t\t\tconst dx = p.point[0] - (p0.point[0] + (p1.point[0] - p0.point[0]) * t);\n\t\t\t\tconst dy = p.point[1] - (p0.point[1] + (p1.point[1] - p0.point[1]) * t);\n\t\t\t\tlet m: AffineMatrix = Math.hypot(dx, dy) > 1e-9 ? translateMatrix(dx, dy) : IDENTITY_MATRIX;\n\t\t\t\tif (blend.orientToSpine) {\n\t\t\t\t\tconst turn = wrapAngle(p.angle - (p0.angle + wrapAngle(p1.angle - p0.angle) * t));\n\t\t\t\t\tif (Math.abs(turn) > 1e-9) {\n\t\t\t\t\t\tconst c = centreOf(step);\n\t\t\t\t\t\tconst origin = { x: c[0] + m[4], y: c[1] + m[5] };\n\t\t\t\t\t\tm = multiplyMatrices(m, rotateMatrix((turn * 180) / Math.PI, origin));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (m !== IDENTITY_MATRIX) step = transformVectorNode(step, m);\n\t\t\t}\n\t\t\tout.push(asStep(step, `${group.id}~${++counter}`));\n\t\t}\n\t});\n\treturn out;\n}\n\nconst childrenCache = new WeakMap<VectorNode, { sig: string; keys: VectorNode[]; children: VectorNode[] }>();\n\nconst sameList = (a: readonly VectorNode[], b: readonly VectorNode[]) => a.length === b.length && a.every((n, i) => n === b[i]);\n\nfunction blendChildren(group: VectorGroupNode): VectorNode[] {\n\tconst keys = vectorBlendKeys(group);\n\tconst sig = `${group.id}|${JSON.stringify(group.blend)}`;\n\tconst cached = keys[0] ? childrenCache.get(keys[0]) : undefined;\n\tlet children: VectorNode[];\n\tif (cached && cached.sig === sig && sameList(cached.keys, keys)) children = cached.children;\n\telse {\n\t\tchildren = generateVectorBlendChildren(group);\n\t\tif (keys[0]) childrenCache.set(keys[0], { sig, keys, children });\n\t}\n\treturn sameList(children, group.children) ? group.children : children;\n}\n\n/**\n * Nodes with every blend group's steps brought up to date (nested blends first). Returns the\n * same array when nothing changed; steps are cached per set of keys, so calling this after\n * every edit is cheap.\n */\nexport function refreshVectorBlends(nodes: readonly VectorNode[]): VectorNode[] {\n\tlet changed = false;\n\tconst out = nodes.map((n) => {\n\t\tif (n.type !== 'group') return n;\n\t\tconst inner = refreshVectorBlends(n.children);\n\t\tlet next: VectorGroupNode = inner === n.children ? n : { ...n, children: inner };\n\t\tif (next.blend) {\n\t\t\tconst children = blendChildren(next);\n\t\t\tif (children !== next.children) next = { ...next, children };\n\t\t}\n\t\tif (next !== n) changed = true;\n\t\treturn next;\n\t});\n\treturn changed ? out : (nodes as VectorNode[]);\n}\n\n/** A step or key with the generated flags dropped and fresh ids. */\nfunction plain(node: VectorNode, newId: () => string): VectorNode {\n\tconst { blendStep: _s, locked: _l, ...rest } = node;\n\tconst renamed = { ...rest, id: newId() } as VectorNode;\n\tif (renamed.type === 'group') renamed.children = renamed.children.map((c) => plain(c, newId));\n\treturn renamed;\n}\n\n/** Expand: the blend as a plain group of its keys and steps (steps get fresh ids). */\nexport function expandVectorBlend(group: VectorGroupNode, newId: () => string): VectorGroupNode {\n\tconst { blend: _b, ...rest } = group;\n\treturn { ...rest, children: group.children.map((c) => (c.blendStep ? plain(c, newId) : c)) };\n}\n\n/**\n * Release: the blend's keys in the group's parent space (the group's transform and opacity\n * applied), followed by the replaced spine as an unpainted path when there is one.\n */\nexport function releaseVectorBlend(group: VectorGroupNode, newId: () => string): VectorNode[] {\n\tconst m = nodeMatrix(group);\n\tconst opacity = group.opacity;\n\tconst keys = vectorBlendKeys(group)\n\t\t.map((c) => (group.transform ? transformVectorNode(c, m) : c))\n\t\t.map((c) => (opacity !== undefined && opacity < 1 ? { ...c, opacity: (c.opacity ?? 1) * opacity } : c));\n\tconst spine = group.blend?.spine;\n\tif (!spine || spine.anchors.length < 2) return keys;\n\tconst path: VectorPathNode = { id: newId(), type: 'path', name: 'Spine', fill: null, stroke: null, subpaths: [{ closed: spine.closed, anchors: spine.anchors.map((a) => ({ ...a })) }] };\n\treturn [...keys, group.transform ? transformVectorNode(path, m) : path];\n}\n","/**\n * Smart guides and object snapping for the vector editor. Targets — anchors, bounding-box\n * edges and centres, path segments and the artboard — are gathered once per gesture in\n * document space (rotated nodes included) and kept sorted by x and y, so each pointer move\n * finds the few near the pointer by bisection. A point snaps, in this order of priority: kept\n * to its Shift angle and slid into line with a target, onto an anchor or centre, onto an\n * intersection of two paths, onto the perpendicular foot or tangent point from where it\n * leaves, onto a path, onto a construction angle, then into line with targets on x and y. A\n * moved box lines its edges and centre up with targets, or spaces itself evenly in its row\n * and column; a resized box lands its moving edges on targets, or matches another object's\n * width or height. Every snap reports labelled guide lines in document units.\n *\n * Portions ported from vectorcraft-main/crates/tools/src/guides.rs (`Targets`, `within`,\n * `for_move`, `for_drawing`, `for_handle`, `for_anchor_drag`, `push_segments`, `snap_from`,\n * `slide`, `lined_up`, `on_segment`, `crossing`, `align_rects`, `snap_rects`, `even_spacing`,\n * `gap_marks`, `snap_scale`, `Leave`), figcraft-main/crates/engine/src/snap.rs (`spacings`,\n * `equal_spacing`, `spacing_guides`: equal-spacing cases and gap labels),\n * designcraft-main/crates/tools/src/snap.rs (`pass_dimensions`: matching other objects'\n * sizes) and cadcraft-main/crates/engine/src/snap/curves.rs (`Param::solve`: perpendicular and\n * tangent points by sign-change sampling and bisection) (MIT OR Apache-2.0, © ArtCraft Team\n * and contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from '../canvas/types.js';\nimport { expandVectorAppearance } from './appearance.js';\nimport { segmentDerivative, segmentNearest, segmentPoint, type BezierSegment } from '../canvas/pathops/bezier.js';\nimport { segmentIntersections } from '../canvas/pathops/boolean.js';\nimport {\n\tapplyMatrix,\n\tIDENTITY_MATRIX,\n\tmatrixRotation,\n\tmultiplyMatrices,\n\tnodeMatrix,\n\trotateMatrix,\n\tsubpathSegment,\n\tsubpathSegmentCount,\n\tvectorNodeBounds,\n\ttype AffineMatrix\n} from './geometry.js';\nimport type { VectorAnchorRef, VectorBounds, VectorDocument, VectorNode, VectorSubpath, VectorTransformHandle, Vec2 } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * What a guide line shows: `align` lines run from a target through the snapped point or box,\n * `construction` lines from where a drawn point leaves, `spacing` marks equal gaps and `size`\n * the side whose length matches another object's.\n */\nexport type VectorGuideKind = 'align' | 'construction' | 'spacing' | 'size';\n\n/** A guide line in document units. */\nexport interface VectorGuideLine {\n\tkind: VectorGuideKind;\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n}\n\n/** What a point or box snapped to. */\nexport type VectorSnapKind = 'anchor' | 'centre' | 'intersect' | 'perpendicular' | 'tangent' | 'path' | 'construction' | 'align' | 'spacing' | 'size';\n\n/** A guide label in document units: the snap kind's name, or a distance for gaps and sizes. */\nexport interface VectorGuideLabel {\n\tkind: VectorSnapKind;\n\tx: number;\n\ty: number;\n\ttext: string;\n}\n\nexport interface VectorSnapGuides {\n\tlines: VectorGuideLine[];\n\tlabels: VectorGuideLabel[];\n}\n\nexport interface VectorPointSnap extends VectorSnapGuides {\n\tpoint: Vec2;\n\t/** The rule that placed the point, or null when nothing was in reach. */\n\tkind: VectorSnapKind | null;\n\t/** Whether x / y were set by a target (for falling back to the grid on the other axis). */\n\tx: boolean;\n\ty: boolean;\n}\n\nexport interface VectorRectSnap extends VectorSnapGuides {\n\t/** Correction to add to the move. */\n\tdx: number;\n\tdy: number;\n\tx: boolean;\n\ty: boolean;\n}\n\nexport interface VectorScaleSnap extends VectorSnapGuides {\n\tsx: number;\n\tsy: number;\n\tsnapped: boolean;\n}\n\n/**\n * The point a drawn point leaves from (the anchor before it, a shape's start, a handle's\n * anchor) and how it keeps to it.\n */\nexport interface VectorSnapLeave {\n\tat: Vec2;\n\t/**\n\t * Shift held: keep to 45° steps round `at` (`angle`), to a diagonal (`square`, a box's\n\t * corner) or to the nearer axis (`axis`, a dragged point).\n\t */\n\tconstrain?: 'angle' | 'square' | 'axis' | null;\n\t/** Construction guide angles through `at`, degrees counter-clockwise on screen. */\n\tangles?: readonly number[];\n\t/** Snap to the perpendicular foot and tangent points from `at` on paths (default true). */\n\tcurves?: boolean;\n}\n\n/** The construction guide angles new editors start with. */\nexport const VECTOR_CONSTRUCTION_ANGLES: readonly number[] = [0, 45, 90, 135];\n\n/** Where `constrain` puts `p` (unchanged without one). */\nexport function constrainToLeave(leave: VectorSnapLeave | null | undefined, p: Vec2): Vec2 {\n\tif (!leave?.constrain) return p;\n\tconst dx = p.x - leave.at.x;\n\tconst dy = p.y - leave.at.y;\n\tswitch (leave.constrain) {\n\t\tcase 'axis':\n\t\t\treturn Math.abs(dx) > Math.abs(dy) ? { x: p.x, y: leave.at.y } : { x: leave.at.x, y: p.y };\n\t\tcase 'square': {\n\t\t\tconst s = Math.max(Math.abs(dx), Math.abs(dy));\n\t\t\treturn { x: leave.at.x + Math.sign(dx || 1) * s, y: leave.at.y + Math.sign(dy || 1) * s };\n\t\t}\n\t\tdefault: {\n\t\t\tconst len = Math.hypot(dx, dy);\n\t\t\tconst a = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);\n\t\t\treturn { x: leave.at.x + Math.cos(a) * len, y: leave.at.y + Math.sin(a) * len };\n\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Targets\n// ---------------------------------------------------------------------------\n\ntype TargetKind = 'anchor' | 'centre' | 'edge' | 'artboard';\n\ninterface PointTarget {\n\tp: Vec2;\n\tkind: 'anchor' | 'centre';\n}\n\n/** A line to line up with: where it runs on its axis and the target point it comes from. */\ninterface LineTarget {\n\tv: number;\n\tfrom: Vec2;\n\tkind: TargetKind;\n}\n\ninterface SegmentTarget {\n\tseg: Point[];\n\tminX: number;\n\tminY: number;\n\tmaxX: number;\n\tmaxY: number;\n}\n\n/** How many anchors, and how many segments, the targets gather at most. */\nconst BUDGET = 20_000;\n/** The first near segments paired up when looking for intersections. */\nconst CROSSING_PAIRS = 32;\n/** Parameter samples per segment for perpendicular / tangent roots, and bisection steps. */\nconst CURVE_SAMPLES = 48;\nconst BISECT = 50;\nconst EQUAL = 1e-6;\n\n/** First index of `sorted` whose key is not below `v` (`strict`: above `v`). */\nfunction bound<T>(sorted: readonly T[], key: (e: T) => number, v: number, strict: boolean): number {\n\tlet lo = 0;\n\tlet hi = sorted.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tconst k = key(sorted[mid]);\n\t\tif (strict ? k <= v : k < v) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\treturn lo;\n}\n\n/** The entries of `sorted` (ascending by `key`) whose key is within `tol` of `v`, by bisection. */\nfunction within<T>(sorted: readonly T[], key: (e: T) => number, v: number, tol: number): readonly T[] {\n\treturn sorted.slice(bound(sorted, key, v - tol, false), bound(sorted, key, v + tol, true));\n}\n\nconst linesNear = (lines: readonly LineTarget[], v: number, tol: number) => within(lines, (l) => l.v, v, tol);\n\nconst distance = (a: Vec2, b: Vec2) => Math.hypot(a.x - b.x, a.y - b.y);\nconst toPoint = (p: Vec2): Point => [p.x, p.y];\nconst toVec = (p: Point): Vec2 => ({ x: p[0], y: p[1] });\n\n/** What `collect` gathers from each path besides its anchors and bounds. */\ntype Gather = 'bounds' | 'segments' | 'drawing';\n\nconst LABELS: Record<VectorSnapKind, string> = {\n\tanchor: 'anchor',\n\tcentre: 'centre',\n\tintersect: 'intersect',\n\tperpendicular: 'perpendicular',\n\ttangent: 'tangent',\n\tpath: 'path',\n\tconstruction: 'angle',\n\talign: 'align',\n\tspacing: 'spacing',\n\tsize: 'size'\n};\n\nconst fmtLength = (n: number) => String(Math.round(n * 100) / 100);\n\n/** A node's own (possibly rotated) frame: its size there and its centre in document space. */\nfunction nodeFrame(node: VectorNode, parent: AffineMatrix, aabb?: VectorBounds | null): { width: number; height: number; centre: Vec2 } | null {\n\tconst angle = matrixRotation(multiplyMatrices(nodeMatrix(node), parent));\n\tconst b = angle ? vectorNodeBounds(node, multiplyMatrices(parent, rotateMatrix(-angle))) : aabb !== undefined ? aabb : vectorNodeBounds(node, parent);\n\tif (!b) return null;\n\tconst c = { x: b.x + b.width / 2, y: b.y + b.height / 2 };\n\treturn { width: b.width, height: b.height, centre: angle ? applyMatrix(rotateMatrix(angle), c) : c };\n}\n\n/** True when `id` is `node` or inside it. */\nfunction contains(node: VectorNode, ids: ReadonlySet<string>): boolean {\n\tif (ids.has(node.id)) return true;\n\treturn node.type === 'group' && node.children.some((c) => contains(c, ids));\n}\n\n/**\n * Snap targets in document space, gathered once per gesture. Build them with `forMove`,\n * `forResize`, `forDrawing`, `forAnchorDrag` or `forHandle`, then call `snapFrom`,\n * `snapRect` or `snapScale` on every pointer move. Tolerances are in document units (screen\n * pixels divided by the zoom).\n */\nexport class VectorSnapTargets {\n\tprivate readonly points: PointTarget[] = [];\n\tprivate readonly xs: LineTarget[] = [];\n\tprivate readonly ys: LineTarget[] = [];\n\tprivate readonly segments: SegmentTarget[] = [];\n\t/** Bounds of the objects a moved box spaces itself evenly with. */\n\tprivate readonly boxes: VectorBounds[] = [];\n\t/** Side lengths of the other objects (in their own frames), which a resize may match. */\n\tprivate readonly sizes: number[] = [];\n\n\tprivate constructor() {}\n\n\t/** How many targets of each sort were gathered. */\n\tget counts(): { points: number; lines: number; segments: number; boxes: number } {\n\t\treturn { points: this.points.length, lines: this.xs.length + this.ys.length, segments: this.segments.length, boxes: this.boxes.length };\n\t}\n\n\t/** Targets for moving nodes `ids`: the other art's anchors, bounds and centres, the artboard, and boxes to space evenly with. */\n\tstatic forMove(doc: VectorDocument, ids: readonly string[]): VectorSnapTargets {\n\t\treturn VectorSnapTargets.collect(doc, new Set(ids), 'bounds');\n\t}\n\n\t/** Targets for resizing nodes `ids`: as for a move, plus the other objects' sizes. */\n\tstatic forResize(doc: VectorDocument, ids: readonly string[]): VectorSnapTargets {\n\t\treturn VectorSnapTargets.collect(doc, new Set(ids), 'bounds');\n\t}\n\n\t/**\n\t * Targets for a drawn point (pen, shape tools): the art but `exclude`, its anchors (as\n\t * points and in line), centres, edges and segments. The anchors and segments of `exclude`\n\t * (the path being drawn, whose bounds grow with it) stay targets.\n\t */\n\tstatic forDrawing(doc: VectorDocument, exclude: readonly string[] = []): VectorSnapTargets {\n\t\tconst skip = new Set(exclude);\n\t\tconst t = VectorSnapTargets.collect(doc, skip, 'drawing', false);\n\t\tt.pushPaths(doc, skip, () => false, true);\n\t\treturn t.index();\n\t}\n\n\t/**\n\t * Targets for dragging anchors: the other art with its segments, and the anchors and\n\t * segments that stay put on the paths being reshaped (their bounds move along, so they are\n\t * no targets). The moving anchors are left out.\n\t */\n\tstatic forAnchorDrag(doc: VectorDocument, anchors: readonly VectorAnchorRef[]): VectorSnapTargets {\n\t\tconst moving = new Map<string, Set<string>>();\n\t\tfor (const r of anchors) {\n\t\t\tconst set = moving.get(r.nodeId) ?? new Set<string>();\n\t\t\tset.add(`${r.subpath}:${r.index}`);\n\t\t\tmoving.set(r.nodeId, set);\n\t\t}\n\t\tconst t = VectorSnapTargets.collect(doc, new Set(moving.keys()), 'segments', false);\n\t\tt.pushPaths(doc, new Set(moving.keys()), (id, s, i) => !!moving.get(id)?.has(`${s}:${i}`), true);\n\t\treturn t.index();\n\t}\n\n\t/**\n\t * Targets for dragging a handle of path `nodeId`: the other art and that path's anchors\n\t * (its bounds and the segments through the handle move with it, so they would chase it).\n\t */\n\tstatic forHandle(doc: VectorDocument, nodeId: string): VectorSnapTargets {\n\t\tconst skip = new Set([nodeId]);\n\t\tconst t = VectorSnapTargets.collect(doc, skip, 'bounds', false);\n\t\tt.pushPaths(doc, skip, () => false, false);\n\t\treturn t.index();\n\t}\n\n\t/** The anchors of the paths `ids` as targets (on them and in line), and their segments when `segments`. */\n\tprivate pushPaths(doc: VectorDocument, ids: ReadonlySet<string>, moving: (id: string, subpath: number, index: number) => boolean, segments: boolean) {\n\t\tlet anchors = BUDGET;\n\t\tlet segmentBudget = segments ? BUDGET : 0;\n\t\tconst visit = (list: readonly VectorNode[], parent: AffineMatrix) => {\n\t\t\tfor (const n of list) {\n\t\t\t\tconst m = multiplyMatrices(nodeMatrix(n), parent);\n\t\t\t\tif (n.type === 'group') visit(n.children, m);\n\t\t\t\telse if (ids.has(n.id) && n.visible !== false) {\n\t\t\t\t\tn.subpaths.forEach((sp, s) => {\n\t\t\t\t\t\tsp.anchors.forEach((a, i) => {\n\t\t\t\t\t\t\tif (anchors <= 0 || moving(n.id, s, i)) return;\n\t\t\t\t\t\t\tthis.pushAnchor(applyMatrix(m, a));\n\t\t\t\t\t\t\tanchors--;\n\t\t\t\t\t\t});\n\t\t\t\t\t\tsegmentBudget = this.pushSegments(sp, m, (i) => moving(n.id, s, i), segmentBudget);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tvisit(doc.nodes, IDENTITY_MATRIX);\n\t}\n\n\tprivate pushAnchor(p: Vec2) {\n\t\tthis.points.push({ p, kind: 'anchor' });\n\t\tthis.xs.push({ v: p.x, from: p, kind: 'anchor' });\n\t\tthis.ys.push({ v: p.y, from: p, kind: 'anchor' });\n\t}\n\n\t/** The segments of `sp` (mapped by `m`) that stay put while the anchors `moving` accepts move. */\n\tprivate pushSegments(sp: VectorSubpath, m: AffineMatrix, moving: (index: number) => boolean, budget: number): number {\n\t\tconst n = sp.anchors.length;\n\t\tconst count = subpathSegmentCount(sp);\n\t\tfor (let i = 0; i < count && budget > 0; i++) {\n\t\t\tif (moving(i) || moving((i + 1) % n)) continue;\n\t\t\tconst seg = subpathSegment(sp, i).map((q) => toPoint(applyMatrix(m, toVec(q))));\n\t\t\tlet minX = Infinity;\n\t\t\tlet minY = Infinity;\n\t\t\tlet maxX = -Infinity;\n\t\t\tlet maxY = -Infinity;\n\t\t\tfor (const [x, y] of seg) {\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t}\n\t\t\tthis.segments.push({ seg, minX, minY, maxX, maxY });\n\t\t\tbudget--;\n\t\t}\n\t\treturn budget;\n\t}\n\n\tprivate pushRect(r: VectorBounds, kind: TargetKind, centre: Vec2 | null) {\n\t\tconst corners = [\n\t\t\t{ x: r.x, y: r.y },\n\t\t\t{ x: r.x + r.width, y: r.y },\n\t\t\t{ x: r.x + r.width, y: r.y + r.height },\n\t\t\t{ x: r.x, y: r.y + r.height }\n\t\t];\n\t\tfor (const p of corners) {\n\t\t\tthis.xs.push({ v: p.x, from: p, kind });\n\t\t\tthis.ys.push({ v: p.y, from: p, kind });\n\t\t}\n\t\tif (centre) {\n\t\t\tthis.points.push({ p: centre, kind: 'centre' });\n\t\t\tthis.xs.push({ v: centre.x, from: centre, kind: 'centre' });\n\t\t\tthis.ys.push({ v: centre.y, from: centre, kind: 'centre' });\n\t\t}\n\t}\n\n\t/** Sorts the targets for `within` (stable: of targets alike, the first gathered wins). */\n\tprivate index(): this {\n\t\tthis.points.sort((a, b) => a.p.x - b.p.x);\n\t\tthis.xs.sort((a, b) => a.v - b.v);\n\t\tthis.ys.sort((a, b) => a.v - b.v);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gathers targets from the artboard and every visible node but `exclude` (and what is inside\n\t * those): each leaf's anchors (also as lines for `drawing`), its segments (`segments`,\n\t * `drawing`), its bounds' corners as lines and its centre; and the top-level objects' boxes and\n\t * sizes (inside a group holding an excluded node, that group's other members).\n\t */\n\tprivate static collect(doc: VectorDocument, exclude: ReadonlySet<string>, gather: Gather, sort = true): VectorSnapTargets {\n\t\tconst self = new VectorSnapTargets();\n\t\tconst artboard = { x: 0, y: 0, width: doc.width, height: doc.height };\n\t\tself.pushRect(artboard, 'artboard', { x: doc.width / 2, y: doc.height / 2 });\n\t\tlet anchors = BUDGET;\n\t\tlet segments = BUDGET;\n\t\tconst visit = (list: readonly VectorNode[], parent: AffineMatrix) => {\n\t\t\tfor (const stored of list) {\n\t\t\t\tif (exclude.has(stored.id) || stored.visible === false) continue;\n\t\t\t\t// Other art snaps where it draws (path effects and live booleans applied).\n\t\t\t\tconst n = contains(stored, exclude) ? stored : expandVectorAppearance(stored);\n\t\t\t\tconst m = multiplyMatrices(nodeMatrix(n), parent);\n\t\t\t\tif (n.type === 'group') {\n\t\t\t\t\tvisit(n.children, m);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (anchors <= 0) continue;\n\t\t\t\tfor (const sp of n.subpaths) {\n\t\t\t\t\tfor (const a of sp.anchors) {\n\t\t\t\t\t\tif (anchors-- <= 0) break;\n\t\t\t\t\t\tconst p = applyMatrix(m, a);\n\t\t\t\t\t\tif (gather === 'drawing') self.pushAnchor(p);\n\t\t\t\t\t\telse self.points.push({ p, kind: 'anchor' });\n\t\t\t\t\t}\n\t\t\t\t\tif (gather !== 'bounds') segments = self.pushSegments(sp, m, () => false, segments);\n\t\t\t\t}\n\t\t\t\tconst b = vectorNodeBounds(n, parent);\n\t\t\t\tconst f = nodeFrame(n, parent, b);\n\t\t\t\tif (b) self.pushRect(b, 'edge', f?.centre ?? null);\n\t\t\t}\n\t\t};\n\t\tvisit(doc.nodes, IDENTITY_MATRIX);\n\t\t// Boxes to space evenly with and sizes to match: whole objects, opening groups that hold an excluded node.\n\t\tconst objects = (list: readonly VectorNode[], parent: AffineMatrix) => {\n\t\t\tfor (const n of list) {\n\t\t\t\tif (exclude.has(n.id) || n.visible === false || self.boxes.length >= BUDGET) continue;\n\t\t\t\tif (n.type === 'group' && contains(n, exclude)) {\n\t\t\t\t\tobjects(n.children, multiplyMatrices(nodeMatrix(n), parent));\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst b = vectorNodeBounds(n, parent);\n\t\t\t\tif (b) self.boxes.push(b);\n\t\t\t\tconst f = nodeFrame(n, parent, b);\n\t\t\t\tif (f) self.sizes.push(f.width, f.height);\n\t\t\t}\n\t\t};\n\t\tobjects(doc.nodes, IDENTITY_MATRIX);\n\t\treturn sort ? self.index() : self;\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Points\n\t// -------------------------------------------------------------------------\n\n\t/** Snaps a single point (see `snapFrom`). */\n\tsnapPoint(p: Vec2, tol: number): VectorPointSnap {\n\t\treturn this.snapFrom(p, null, tol);\n\t}\n\n\t/**\n\t * Snaps a point `p`, drawn or dragged from `leave` when given. With `leave.constrain` it keeps\n\t * to its angle round `leave.at`, sliding along that way into line with the nearest target.\n\t * Otherwise it lands, in order, on an anchor or centre, an intersection of two paths, the\n\t * perpendicular foot or a tangent point from `leave.at`, a path, a construction guide\n\t * through `leave.at` (where a target's line crosses it if one does nearby), or else lines up\n\t * with targets on x and y.\n\t */\n\tsnapFrom(p: Vec2, leave: VectorSnapLeave | null | undefined, tol: number): VectorPointSnap {\n\t\tconst none = (q: Vec2): VectorPointSnap => ({ point: q, kind: null, x: false, y: false, lines: [], labels: [] });\n\t\tconst at = (q: Vec2, kind: VectorSnapKind, lines: VectorGuideLine[] = []): VectorPointSnap => ({\n\t\t\tpoint: q,\n\t\t\tkind,\n\t\t\tx: true,\n\t\t\ty: true,\n\t\t\tlines,\n\t\t\tlabels: [this.label(q, kind)]\n\t\t});\n\t\tif (leave?.constrain) {\n\t\t\tconst q = constrainToLeave(leave, p);\n\t\t\tconst hit = this.slide(leave.at, { x: q.x - leave.at.x, y: q.y - leave.at.y }, q, tol, true);\n\t\t\treturn hit ? { point: hit.point, x: true, y: true, ...this.linedUp(hit.point, hit.from) } : none(q);\n\t\t}\n\t\tconst point = this.nearestPoint(p, tol);\n\t\tif (point) return at(point.p, point.kind);\n\t\tconst near = this.segmentsNear(p, tol);\n\t\tconst crossing = this.crossing(near, p, tol);\n\t\tif (crossing) return at(crossing, 'intersect');\n\t\tif (leave && leave.curves !== false) {\n\t\t\tconst foot = this.curvePoint(near, leave.at, p, tol);\n\t\t\tif (foot) return at(foot.point, foot.kind, [line('construction', leave.at, foot.point)]);\n\t\t}\n\t\tconst on = this.onSegment(near);\n\t\tif (on) return at(on, 'path');\n\t\tif (leave?.angles?.length) {\n\t\t\tconst c = construction(leave, p, tol);\n\t\t\tif (c) {\n\t\t\t\tconst hit = this.slide(leave.at, c.dir, c.point, tol, false);\n\t\t\t\tif (hit) {\n\t\t\t\t\tconst lined = this.linedUp(hit.point, hit.from);\n\t\t\t\t\treturn { point: hit.point, x: true, y: true, kind: lined.kind, lines: [line('construction', leave.at, hit.point), ...lined.lines], labels: lined.labels };\n\t\t\t\t}\n\t\t\t\treturn { point: c.point, kind: 'construction', x: true, y: true, lines: [line('construction', leave.at, c.point)], labels: [] };\n\t\t\t}\n\t\t}\n\t\tconst nearest = (lines: readonly LineTarget[], v: number) => {\n\t\t\tlet best: LineTarget | null = null;\n\t\t\tfor (const l of linesNear(lines, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.v - v)) best = l;\n\t\t\treturn best;\n\t\t};\n\t\tconst bx = nearest(this.xs, p.x);\n\t\tconst by = nearest(this.ys, p.y);\n\t\tif (!bx && !by) return none(p);\n\t\tconst out = { x: bx ? bx.v : p.x, y: by ? by.v : p.y };\n\t\tconst lines: VectorGuideLine[] = [];\n\t\tif (bx) lines.push(line('align', bx.from, { x: out.x, y: out.y }));\n\t\tif (by) lines.push(line('align', by.from, { x: out.x, y: out.y }));\n\t\treturn { point: out, kind: 'align', x: !!bx, y: !!by, lines, labels: [this.label(out, 'align')] };\n\t}\n\n\tprivate label(p: Vec2, kind: VectorSnapKind, text = LABELS[kind]): VectorGuideLabel {\n\t\treturn { kind, x: p.x, y: p.y, text };\n\t}\n\n\t/** The anchor or centre nearest `p`, within `tol`. */\n\tprivate nearestPoint(p: Vec2, tol: number): PointTarget | null {\n\t\tlet best: PointTarget | null = null;\n\t\tlet bestD = Infinity;\n\t\tfor (const t of within(this.points, (e) => e.p.x, p.x, tol)) {\n\t\t\tconst d = distance(t.p, p);\n\t\t\tif (d <= tol && d < bestD) {\n\t\t\t\tbest = t;\n\t\t\t\tbestD = d;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\t/**\n\t * Slides `q`, on the line through `at` along `dir` (beyond `at` only for a `ray`), to where\n\t * the nearest target line crosses it, at most twice `tol` away: where it lands and the\n\t * target point that line comes from.\n\t */\n\tprivate slide(at: Vec2, dir: Vec2, q: Vec2, tol: number, ray: boolean): { point: Vec2; from: Vec2 } | null {\n\t\tconst len2 = dir.x * dir.x + dir.y * dir.y;\n\t\tif (!Number.isFinite(len2) || len2 <= 1e-18) return null;\n\t\tconst reach = 2 * tol;\n\t\tlet best: { moved: number; point: Vec2; from: Vec2 } | null = null;\n\t\tfor (const vertical of [true, false]) {\n\t\t\tconst d = vertical ? dir.x : dir.y;\n\t\t\tif (Math.abs(d) <= 1e-9) continue;\n\t\t\tconst a = vertical ? at.x : at.y;\n\t\t\tfor (const l of linesNear(vertical ? this.xs : this.ys, vertical ? q.x : q.y, reach)) {\n\t\t\t\tconst s = (l.v - a) / d;\n\t\t\t\t// Exactly on the line, whatever the rounding along the way.\n\t\t\t\tconst hit = vertical ? { x: l.v, y: at.y + dir.y * s } : { x: at.x + dir.x * s, y: l.v };\n\t\t\t\tconst moved = distance(hit, q);\n\t\t\t\tif (moved <= reach && (!ray || s > 0) && (!best || moved < best.moved)) best = { moved, point: hit, from: l.from };\n\t\t\t}\n\t\t}\n\t\treturn best && { point: best.point, from: best.from };\n\t}\n\n\t/** The guides of a point `q` slid into line with the target at `from`: the line and \"align\", or the target's own label when `q` sits on it. */\n\tprivate linedUp(q: Vec2, from: Vec2): { kind: VectorSnapKind } & VectorSnapGuides {\n\t\tconst on = this.nearestPoint(q, 1e-9);\n\t\tif (on) return { kind: on.kind, lines: [], labels: [this.label(on.p, on.kind)] };\n\t\treturn { kind: 'align', lines: [line('align', from, q)], labels: [this.label(q, 'align')] };\n\t}\n\n\t/** Target segments whose control box reaches `p` and which pass within `tol` of it, nearest first. */\n\tprivate segmentsNear(p: Vec2, tol: number): { seg: Point[]; t: number; distance: number }[] {\n\t\tconst out: { seg: Point[]; t: number; distance: number }[] = [];\n\t\tconst pt = toPoint(p);\n\t\tfor (const s of this.segments) {\n\t\t\tif (p.x < s.minX - tol || p.x > s.maxX + tol || p.y < s.minY - tol || p.y > s.maxY + tol) continue;\n\t\t\tconst hit = segmentNearest(s.seg, pt);\n\t\t\tif (hit.distance <= tol) out.push({ seg: s.seg, t: hit.t, distance: hit.distance });\n\t\t}\n\t\treturn out.sort((a, b) => a.distance - b.distance);\n\t}\n\n\tprivate onSegment(near: readonly { seg: Point[]; t: number }[]): Vec2 | null {\n\t\treturn near.length ? toVec(segmentPoint(near[0].seg, near[0].t)) : null;\n\t}\n\n\t/** Where two of the target segments near `p` cross, the crossing nearest `p` within `tol`. */\n\tprivate crossing(near: readonly { seg: Point[] }[], p: Vec2, tol: number): Vec2 | null {\n\t\tconst list = near.slice(0, CROSSING_PAIRS);\n\t\tlet best: Vec2 | null = null;\n\t\tlet bestD = Infinity;\n\t\tfor (let i = 0; i < list.length; i++) {\n\t\t\tfor (let j = i + 1; j < list.length; j++) {\n\t\t\t\tfor (const hit of segmentIntersections(list[i].seg as BezierSegment, list[j].seg as BezierSegment)) {\n\t\t\t\t\tconst q = toVec(hit.point);\n\t\t\t\t\tconst d = distance(q, p);\n\t\t\t\t\tif (d <= tol && d < bestD) {\n\t\t\t\t\t\tbest = q;\n\t\t\t\t\t\tbestD = d;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\t/**\n\t * The foot of a perpendicular, or a tangent point, from `base` on a target segment near `p`\n\t * (within `tol` of it): roots of `(B(t) − base) · B′(t)` and `(B(t) − base) × B′(t)`\n\t * bracketed by sign changes of samples and narrowed by bisection.\n\t */\n\tprivate curvePoint(near: readonly { seg: Point[] }[], base: Vec2, p: Vec2, tol: number): { point: Vec2; kind: 'perpendicular' | 'tangent' } | null {\n\t\tlet best: { point: Vec2; kind: 'perpendicular' | 'tangent'; d: number } | null = null;\n\t\tfor (const { seg } of near) {\n\t\t\tfor (const kind of ['perpendicular', 'tangent'] as const) {\n\t\t\t\tif (kind === 'tangent' && seg.length === 2) continue;\n\t\t\t\tfor (const q of solveOnSegment(seg, base, kind === 'perpendicular' ? dotCondition : crossCondition)) {\n\t\t\t\t\tconst d = distance(q, p);\n\t\t\t\t\tif (d <= tol && (!best || d < best.d)) best = { point: q, kind, d };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn best && { point: best.point, kind: best.kind };\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Boxes\n\t// -------------------------------------------------------------------------\n\n\t/**\n\t * Snaps a moving box `r` (the moved selection's bounds): its edge or centre nearest a target\n\t * line on each axis (`axes` limits which may move). With `spacing`, an axis that lines up\n\t * with nothing spaces the box evenly in its row or column. Returns the correction and guides.\n\t */\n\tsnapRect(r: VectorBounds, tol: number, options: { spacing?: boolean; axes?: { x?: boolean; y?: boolean } } = {}): VectorRectSnap {\n\t\tconst axes = { x: options.axes?.x !== false, y: options.axes?.y !== false };\n\t\tconst nearest = (lines: readonly LineTarget[], values: number[]) => {\n\t\t\tlet best: { d: number; from: Vec2 } | null = null;\n\t\t\tfor (const v of values) for (const l of linesNear(lines, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.d)) best = { d: l.v - v, from: l.from };\n\t\t\treturn best;\n\t\t};\n\t\tconst bx = axes.x ? nearest(this.xs, [r.x, r.x + r.width / 2, r.x + r.width]) : null;\n\t\tconst by = axes.y ? nearest(this.ys, [r.y, r.y + r.height / 2, r.y + r.height]) : null;\n\t\tconst out: VectorRectSnap = { dx: bx?.d ?? 0, dy: by?.d ?? 0, x: !!bx, y: !!by, lines: [], labels: [] };\n\t\tconst done = { ...r, x: r.x + out.dx, y: r.y + out.dy };\n\t\tif (bx) {\n\t\t\tconst x = bx.from.x;\n\t\t\tout.lines.push({ kind: 'align', x1: x, y1: Math.min(bx.from.y, done.y), x2: x, y2: Math.max(bx.from.y, done.y + done.height) });\n\t\t}\n\t\tif (by) {\n\t\t\tconst y = by.from.y;\n\t\t\tout.lines.push({ kind: 'align', x1: Math.min(by.from.x, done.x), y1: y, x2: Math.max(by.from.x, done.x + done.width), y2: y });\n\t\t}\n\t\tif (options.spacing && this.boxes.length) {\n\t\t\tfor (const horizontal of [true, false]) {\n\t\t\t\tif (horizontal ? out.x || !axes.x : out.y || !axes.y) continue;\n\t\t\t\tconst moved = { ...r, x: r.x + out.dx, y: r.y + out.dy };\n\t\t\t\tconst even = this.evenSpacing(moved, horizontal, tol);\n\t\t\t\tif (!even) continue;\n\t\t\t\tif (horizontal) {\n\t\t\t\t\tout.dx += even.shift;\n\t\t\t\t\tout.x = true;\n\t\t\t\t} else {\n\t\t\t\t\tout.dy += even.shift;\n\t\t\t\t\tout.y = true;\n\t\t\t\t}\n\t\t\t\tfor (const gap of even.gaps) this.gapMarks(gap, horizontal, tol, out);\n\t\t\t}\n\t\t}\n\t\tif (bx || by) out.labels.unshift(this.label({ x: r.x + out.dx + r.width / 2, y: r.y + out.dy + r.height / 2 }, 'align'));\n\t\treturn out;\n\t}\n\n\t/**\n\t * Equal spacing along x (`horizontal`) or y for the moved box `r`, among the objects of its\n\t * row (column): the shift within `tol` that centres it between its neighbours, or leaves it\n\t * as far from the neighbour before or after it as two other objects of the row are apart;\n\t * and the gaps that are equal then, as (start, end, where across).\n\t */\n\tprivate evenSpacing(r: VectorBounds, horizontal: boolean, tol: number): { shift: number; gaps: [number, number, number][] } | null {\n\t\tconst span = (b: VectorBounds, x: boolean): [number, number] => (x ? [b.x, b.x + b.width] : [b.y, b.y + b.height]);\n\t\tconst [r0, r1] = span(r, horizontal);\n\t\tconst len = r1 - r0;\n\t\tconst [c0, c1] = span(r, !horizontal);\n\t\tconst row = this.boxes\n\t\t\t.map((b) => ({ b, a0: span(b, horizontal)[0], a1: span(b, horizontal)[1] }))\n\t\t\t.filter((e) => span(e.b, !horizontal)[0] < c1 && span(e.b, !horizontal)[1] > c0 && (e.a1 <= r0 + tol || e.a0 >= r1 - tol))\n\t\t\t.sort((a, b) => a.a0 - b.a0);\n\t\tif (!row.length) return null;\n\t\t// Across the middle of what two boxes share (or of the space between them).\n\t\tconst mid = (a: VectorBounds, b: VectorBounds) => (Math.max(span(a, !horizontal)[0], span(b, !horizontal)[0]) + Math.min(span(a, !horizontal)[1], span(b, !horizontal)[1])) / 2;\n\t\tconst pairs: { g: number; gap: [number, number, number] }[] = [];\n\t\tfor (const p of row) {\n\t\t\tconst next = bound(row, (e) => e.a0, p.a1, false);\n\t\t\tconst q = row[next];\n\t\t\tif (!q) continue;\n\t\t\tconst straddles = p.a1 <= r0 + tol && q.a0 >= r1 - tol;\n\t\t\tif (q.a0 > p.a1 && !straddles) pairs.push({ g: q.a0 - p.a1, gap: [p.a1, q.a0, mid(p.b, q.b)] });\n\t\t}\n\t\tlet before: (typeof row)[number] | null = null;\n\t\tlet after: (typeof row)[number] | null = null;\n\t\tfor (const e of row) {\n\t\t\tif (e.a1 <= r0 + tol && (!before || e.a1 > before.a1)) before = e;\n\t\t\tif (e.a0 >= r1 - tol && (!after || e.a0 < after.a0)) after = e;\n\t\t}\n\t\tconst starts: { start: number; repeats: number | null }[] = [];\n\t\tif (before && after && after.a0 - before.a1 > len) starts.push({ start: (before.a1 + after.a0 - len) / 2, repeats: null });\n\t\tfor (const { g } of pairs) {\n\t\t\tif (before) starts.push({ start: before.a1 + g, repeats: g });\n\t\t\tif (after) starts.push({ start: after.a0 - g - len, repeats: g });\n\t\t}\n\t\tlet pick: { start: number; repeats: number | null } | null = null;\n\t\tfor (const s of starts) if (Math.abs(s.start - r0) <= tol && (!pick || Math.abs(s.start - r0) < Math.abs(pick.start - r0))) pick = s;\n\t\tif (!pick) return null;\n\t\tconst { start, repeats } = pick;\n\t\tconst at = horizontal ? { ...r, x: start } : { ...r, y: start };\n\t\tconst end = start + len;\n\t\tconst gaps: [number, number, number][] = [];\n\t\tif (before && (repeats === null || Math.abs(start - before.a1 - repeats) <= EQUAL)) gaps.push([before.a1, start, mid(before.b, at)]);\n\t\tif (after && (repeats === null || Math.abs(after.a0 - end - repeats) <= EQUAL)) gaps.push([end, after.a0, mid(after.b, at)]);\n\t\tif (repeats !== null) for (const p of pairs) if (Math.abs(p.g - repeats) <= EQUAL) gaps.push(p.gap);\n\t\treturn { shift: start - r0, gaps };\n\t}\n\n\t/** A gap mark: a line across the gap with a tick at each end, and its length as a label. */\n\tprivate gapMarks([u0, u1, c]: [number, number, number], horizontal: boolean, tick: number, out: VectorSnapGuides) {\n\t\tconst pt = (u: number, v: number): Vec2 => (horizontal ? { x: u, y: v } : { x: v, y: u });\n\t\tout.lines.push(line('spacing', pt(u0, c), pt(u1, c)), line('spacing', pt(u0, c - tick), pt(u0, c + tick)), line('spacing', pt(u1, c - tick), pt(u1, c + tick)));\n\t\tout.labels.push(this.label(pt((u0 + u1) / 2, c), 'spacing', fmtLength(u1 - u0)));\n\t}\n\n\t/** The other objects' side length nearest `length`, within `tol`, or null. */\n\tmatchSize(length: number, tol: number): number | null {\n\t\tlet best: number | null = null;\n\t\tfor (const s of this.sizes) if (Math.abs(s - length) <= tol && (best === null || Math.abs(s - length) < Math.abs(best - length))) best = s;\n\t\treturn best;\n\t}\n\n\t/**\n\t * Snaps a selection-box resize. `sx / sy` are the scale `scaleFor` gave for dragging\n\t * `handle` of `frame` (in the box's own, possibly rotated frame), `mods.shift` keeps the\n\t * proportions and `mods.alt` scales about the centre. The handle lands on an anchor or\n\t * centre within `tol`, else the corner or the side it moves lines up with the nearest\n\t * target, whatever the box's angle; a handle with one way to go (a side, or a proportional\n\t * corner) slides along it until one of them is on a target. An axis that snaps to nothing\n\t * may match another object's width or height instead.\n\t */\n\tsnapScale(\n\t\tframe: VectorBounds & { angle: number },\n\t\thandle: Exclude<VectorTransformHandle, 'rotate'>,\n\t\tsx: number,\n\t\tsy: number,\n\t\tmods: { shift?: boolean; alt?: boolean },\n\t\ttol: number\n\t): VectorScaleSnap {\n\t\tconst toDoc = frame.angle ? rotateMatrix(frame.angle) : IDENTITY_MATRIX;\n\t\tconst toLocal = frame.angle ? rotateMatrix(-frame.angle) : IDENTITY_MATRIX;\n\t\tconst corner = handle.length === 2;\n\t\tconst proportional = !!mods.shift;\n\t\tconst centre = { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 };\n\t\tconst origin = mods.alt ? centre : handlePos(opposite(handle), frame);\n\t\tconst hp = handlePos(handle, frame);\n\t\tconst d0 = { x: hp.x - origin.x, y: hp.y - origin.y };\n\t\tconst valid = (n: number, s: number) => Number.isFinite(n) && n * s > 0;\n\t\tconst nearest = (v: number, lines: readonly LineTarget[]) => {\n\t\t\tlet best: LineTarget | null = null;\n\t\t\tfor (const l of linesNear(lines, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.v - v)) best = l;\n\t\t\treturn best;\n\t\t};\n\t\ttype Snap = { hx: LineTarget | null; hy: LineTarget | null; on: PointTarget | null };\n\t\tlet nx = sx;\n\t\tlet ny = sy;\n\t\tlet snap: Snap = { hx: null, hy: null, on: null };\n\t\tif (corner && !proportional) {\n\t\t\t// The corner goes anywhere: onto an anchor or centre, else it takes a target's x, its y, or both.\n\t\t\tconst p = applyMatrix(toDoc, { x: origin.x + d0.x * sx, y: origin.y + d0.y * sy });\n\t\t\tconst hx = nearest(p.x, this.xs);\n\t\t\tconst hy = nearest(p.y, this.ys);\n\t\t\tconst on = this.nearestPoint(p, tol);\n\t\t\tconst tries: { tx: number | null; ty: number | null; s: Snap }[] = [];\n\t\t\tif (on) tries.push({ tx: on.p.x, ty: on.p.y, s: { hx: null, hy: null, on } });\n\t\t\ttries.push({ tx: hx?.v ?? null, ty: hy?.v ?? null, s: { hx, hy, on: null } });\n\t\t\ttries.push({ tx: hx?.v ?? null, ty: null, s: { hx, hy: null, on: null } });\n\t\t\ttries.push({ tx: null, ty: hy?.v ?? null, s: { hx: null, hy, on: null } });\n\t\t\tfor (const { tx, ty, s } of tries) {\n\t\t\t\tif (tx === null && ty === null) continue;\n\t\t\t\tconst q = applyMatrix(toLocal, { x: tx ?? p.x, y: ty ?? p.y });\n\t\t\t\tlet cx = (q.x - origin.x) / d0.x;\n\t\t\t\tlet cy = (q.y - origin.y) / d0.y;\n\t\t\t\t// Square to the page, the axis without a target stays exactly where it was.\n\t\t\t\tif (!frame.angle) {\n\t\t\t\t\tif (tx === null) cx = sx;\n\t\t\t\t\tif (ty === null) cy = sy;\n\t\t\t\t}\n\t\t\t\tif (valid(cx, sx) && valid(cy, sy)) {\n\t\t\t\t\tnx = cx;\n\t\t\t\t\tny = cy;\n\t\t\t\t\tsnap = s;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// One parameter `s`: the scale is `c + s·e` on each axis.\n\t\t\tconst movesX = handle === 'e' || handle === 'w';\n\t\t\tlet e: Vec2;\n\t\t\tlet c: Vec2;\n\t\t\tlet s0: number;\n\t\t\tif (corner) [e, c, s0] = [{ x: Math.sign(sx), y: Math.sign(sy) }, { x: 0, y: 0 }, Math.abs(sx)];\n\t\t\telse if (movesX) [e, c, s0] = proportional ? [{ x: 1, y: Math.sign(sx) }, { x: 0, y: 0 }, sx] : [{ x: 1, y: 0 }, { x: 0, y: 1 }, sx];\n\t\t\telse [e, c, s0] = proportional ? [{ x: Math.sign(sy), y: 1 }, { x: 0, y: 0 }, sy] : [{ x: 0, y: 1 }, { x: 1, y: 0 }, sy];\n\t\t\t// The points that may land on a target: the handle, and the two ends of its side.\n\t\t\tconst i = RING.indexOf(handle);\n\t\t\tconst ends = corner ? [] : [RING[(i + 1) % 8], RING[(i + 7) % 8]];\n\t\t\t// How far the handle travels per unit of `s`: a snap may not drag it far from the pointer.\n\t\t\tconst travel = Math.hypot(e.x * d0.x, e.y * d0.y);\n\t\t\tlet best: { s: number; inLine: boolean; moved: number; snap: Snap } | null = null;\n\t\t\tconst consider = (s: number, inLine: boolean, found: Snap) => {\n\t\t\t\tconst moved = Math.abs(s - s0) * travel;\n\t\t\t\tif (!valid(s, s0) || moved > 2 * tol) return;\n\t\t\t\tif (!best || (inLine === best.inLine ? moved < best.moved : !inLine)) best = { s, inLine, moved, snap: found };\n\t\t\t};\n\t\t\tfor (const h of [handle, ...ends]) {\n\t\t\t\tconst hv = handlePos(h, frame);\n\t\t\t\tconst v = { x: hv.x - origin.x, y: hv.y - origin.y };\n\t\t\t\tconst base = applyMatrix(toDoc, { x: origin.x + c.x * v.x, y: origin.y + c.y * v.y });\n\t\t\t\tconst dir = applyMatrix(toDoc, { x: e.x * v.x, y: e.y * v.y });\n\t\t\t\tconst p = { x: base.x + dir.x * s0, y: base.y + dir.y * s0 };\n\t\t\t\tconst l2 = dir.x * dir.x + dir.y * dir.y;\n\t\t\t\tconst on = l2 > 1e-18 ? this.nearestPoint(p, tol) : null;\n\t\t\t\t// An anchor or centre beside its way: it stops at the nearest point of the way.\n\t\t\t\tif (on) consider(((on.p.x - base.x) * dir.x + (on.p.y - base.y) * dir.y) / l2, false, { hx: null, hy: null, on });\n\t\t\t\tif (Math.abs(dir.x) > 1e-9) {\n\t\t\t\t\tconst hit = nearest(p.x, this.xs);\n\t\t\t\t\tif (hit) consider((hit.v - base.x) / dir.x, true, { hx: hit, hy: null, on: null });\n\t\t\t\t}\n\t\t\t\tif (Math.abs(dir.y) > 1e-9) {\n\t\t\t\t\tconst hit = nearest(p.y, this.ys);\n\t\t\t\t\tif (hit) consider((hit.v - base.y) / dir.y, true, { hx: null, hy: hit, on: null });\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst found = best as { s: number; snap: Snap } | null;\n\t\t\tif (found) {\n\t\t\t\tnx = c.x + found.s * e.x;\n\t\t\t\tny = c.y + found.s * e.y;\n\t\t\t\tsnap = found.snap;\n\t\t\t}\n\t\t}\n\t\tconst out: VectorScaleSnap = { sx: nx, sy: ny, snapped: false, lines: [], labels: [] };\n\t\tconst handleAt = () => applyMatrix(toDoc, { x: origin.x + d0.x * out.sx, y: origin.y + d0.y * out.sy });\n\t\tif (snap.on) {\n\t\t\tout.snapped = true;\n\t\t\tout.labels.push(this.label(snap.on.p, snap.on.kind));\n\t\t\treturn out;\n\t\t}\n\t\t// Sides that line up with nothing may match another object's width or height.\n\t\tconst movesX = handle !== 'n' && handle !== 's';\n\t\tconst movesY = handle !== 'e' && handle !== 'w';\n\t\tconst sized: ('x' | 'y')[] = [];\n\t\tif (!proportional) {\n\t\t\tif (movesX && !snap.hx && !(frame.angle && snap.hy) && frame.width > 1e-9) {\n\t\t\t\tconst len = this.matchSize(Math.abs(out.sx) * frame.width, tol);\n\t\t\t\tif (len !== null && len > 0) {\n\t\t\t\t\tout.sx = Math.sign(out.sx || 1) * (len / frame.width);\n\t\t\t\t\tsized.push('x');\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (movesY && !snap.hy && !(frame.angle && snap.hx) && frame.height > 1e-9) {\n\t\t\t\tconst len = this.matchSize(Math.abs(out.sy) * frame.height, tol);\n\t\t\t\tif (len !== null && len > 0) {\n\t\t\t\t\tout.sy = Math.sign(out.sy || 1) * (len / frame.height);\n\t\t\t\t\tsized.push('y');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// The resized box in its frame, and its corners on the page.\n\t\tconst x0 = origin.x + (frame.x - origin.x) * out.sx;\n\t\tconst x1 = origin.x + (frame.x + frame.width - origin.x) * out.sx;\n\t\tconst y0 = origin.y + (frame.y - origin.y) * out.sy;\n\t\tconst y1 = origin.y + (frame.y + frame.height - origin.y) * out.sy;\n\t\tconst box = { x0: Math.min(x0, x1), x1: Math.max(x0, x1), y0: Math.min(y0, y1), y1: Math.max(y0, y1) };\n\t\tconst page = [\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((q) => applyMatrix(toDoc, q));\n\t\tconst pminY = Math.min(...page.map((q) => q.y));\n\t\tconst pmaxY = Math.max(...page.map((q) => q.y));\n\t\tconst pminX = Math.min(...page.map((q) => q.x));\n\t\tconst pmaxX = Math.max(...page.map((q) => q.x));\n\t\tif (snap.hx) out.lines.push({ kind: 'align', x1: snap.hx.v, y1: Math.min(snap.hx.from.y, pminY), x2: snap.hx.v, y2: Math.max(snap.hx.from.y, pmaxY) });\n\t\tif (snap.hy) out.lines.push({ kind: 'align', x1: Math.min(snap.hy.from.x, pminX), y1: snap.hy.v, x2: Math.max(snap.hy.from.x, pmaxX), y2: snap.hy.v });\n\t\tif (snap.hx || snap.hy) {\n\t\t\tout.snapped = true;\n\t\t\tout.labels.push(this.label(handleAt(), 'align'));\n\t\t}\n\t\tfor (const axis of sized) {\n\t\t\tout.snapped = true;\n\t\t\t// Along the matched side, just outside the box.\n\t\t\tconst off = tol * 2;\n\t\t\tconst [a, b] = axis === 'x' ? [{ x: box.x0, y: box.y1 + off }, { x: box.x1, y: box.y1 + off }] : [{ x: box.x1 + off, y: box.y0 }, { x: box.x1 + off, y: box.y1 }];\n\t\t\tconst pa = applyMatrix(toDoc, a);\n\t\t\tconst pb = applyMatrix(toDoc, b);\n\t\t\tout.lines.push(line('size', pa, pb));\n\t\t\tout.labels.push(this.label({ x: (pa.x + pb.x) / 2, y: (pa.y + pb.y) / 2 }, 'size', `${axis === 'x' ? 'W' : 'H'} ${fmtLength(axis === 'x' ? box.x1 - box.x0 : box.y1 - box.y0)}`));\n\t\t}\n\t\treturn out;\n\t}\n}\n\n/** The eight resize handles clockwise from the top left (each side sits between its two corners). */\nconst RING: readonly Exclude<VectorTransformHandle, 'rotate'>[] = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];\n\nfunction opposite(h: Exclude<VectorTransformHandle, 'rotate'>): Exclude<VectorTransformHandle, 'rotate'> {\n\treturn RING[(RING.indexOf(h) + 4) % 8];\n}\n\nfunction handlePos(h: Exclude<VectorTransformHandle, 'rotate'>, r: VectorBounds): Vec2 {\n\tconst x = h.includes('w') ? r.x : h.includes('e') ? r.x + r.width : r.x + r.width / 2;\n\tconst y = h.startsWith('n') ? r.y : h.startsWith('s') ? r.y + r.height : r.y + r.height / 2;\n\treturn { x, y };\n}\n\nfunction line(kind: VectorGuideKind, a: Vec2, b: Vec2): VectorGuideLine {\n\treturn { kind, x1: a.x, y1: a.y, x2: b.x, y2: b.y };\n}\n\n/** The construction guide through `leave.at` nearest `p`, within `tol` (and `p` farther than that from `at`): its direction and where `p` lands. */\nfunction construction(leave: VectorSnapLeave, p: Vec2, tol: number): { dir: Vec2; point: Vec2 } | null {\n\tconst v = { x: p.x - leave.at.x, y: p.y - leave.at.y };\n\tif (Math.hypot(v.x, v.y) <= tol) return null;\n\tlet best: { dir: Vec2; point: Vec2; d: number } | null = null;\n\tfor (const a of leave.angles ?? []) {\n\t\t// Counter-clockwise as seen on the y-down page.\n\t\tconst r = (a * Math.PI) / 180;\n\t\t// Exactly along an axis for multiples of 90°.\n\t\tconst exact = (n: number) => (Math.abs(n) < 1e-12 ? 0 : n);\n\t\tconst dir = { x: exact(Math.cos(r)), y: exact(-Math.sin(r)) };\n\t\tconst k = v.x * dir.x + v.y * dir.y;\n\t\tconst point = { x: leave.at.x + dir.x * k, y: leave.at.y + dir.y * k };\n\t\tconst d = distance(point, p);\n\t\tif (d <= tol && (!best || d < best.d)) best = { dir, point, d };\n\t}\n\treturn best && { dir: best.dir, point: best.point };\n}\n\nconst dotCondition = (r: Point, d: Point) => r[0] * d[0] + r[1] * d[1];\nconst crossCondition = (r: Point, d: Point) => r[0] * d[1] - r[1] * d[0];\n\n/**\n * The points of a segment where `cond(B(t) − base, B′(t))` is zero: sign changes of samples,\n * narrowed by bisection, kept when the normalised condition is (nearly) zero there.\n */\nfunction solveOnSegment(seg: readonly Point[], base: Vec2, cond: (r: Point, d: Point) => number): Vec2[] {\n\tconst b: Point = [base.x, base.y];\n\tconst g = (t: number) => {\n\t\tconst p = segmentPoint(seg, t);\n\t\treturn cond([p[0] - b[0], p[1] - b[1]], segmentDerivative(seg, t));\n\t};\n\tconst vals: number[] = [];\n\tlet scale = 0;\n\tfor (let i = 0; i <= CURVE_SAMPLES; i++) {\n\t\tconst t = i / CURVE_SAMPLES;\n\t\tconst p = segmentPoint(seg, t);\n\t\tconst d = segmentDerivative(seg, t);\n\t\tconst r: Point = [p[0] - b[0], p[1] - b[1]];\n\t\tvals.push(cond(r, d));\n\t\tscale = Math.max(scale, Math.hypot(r[0], r[1]) * Math.hypot(d[0], d[1]));\n\t}\n\t// Every point qualifies (a line seen from a point on it): no particular solution.\n\tif (!(scale > 0) || vals.every((v) => Math.abs(v) <= scale * 1e-12)) return [];\n\tconst out: Vec2[] = [];\n\tfor (let i = 0; i < CURVE_SAMPLES; i++) {\n\t\tconst ga = vals[i];\n\t\tconst gb = vals[i + 1];\n\t\tif (!(Number.isFinite(ga) && Number.isFinite(gb)) || (ga > 0 === gb > 0 && ga !== 0)) continue;\n\t\tlet lo = i / CURVE_SAMPLES;\n\t\tlet hi = (i + 1) / CURVE_SAMPLES;\n\t\tlet flo = ga;\n\t\tfor (let k = 0; k < BISECT; k++) {\n\t\t\tconst m = (lo + hi) / 2;\n\t\t\tconst fm = g(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 > 0 === flo > 0) {\n\t\t\t\tlo = m;\n\t\t\t\tflo = fm;\n\t\t\t} else hi = m;\n\t\t}\n\t\tconst t = (lo + hi) / 2;\n\t\tconst p = segmentPoint(seg, t);\n\t\tconst dv = segmentDerivative(seg, t);\n\t\tconst r = Math.hypot(p[0] - b[0], p[1] - b[1]);\n\t\tconst dl = Math.hypot(dv[0], dv[1]);\n\t\tif (!(r > 1e-9 && dl > 0) || Math.abs(cond([p[0] - b[0], p[1] - b[1]], dv)) > 1e-6 * r * dl) continue;\n\t\tif (!out.some((q) => Math.hypot(q.x - p[0], q.y - p[1]) <= 1e-9 * (1 + r))) out.push({ x: p[0], y: p[1] });\n\t}\n\treturn out;\n}\n","/**\n * Path editing on subpaths in a node's own space: anchor deletion that keeps the shape,\n * segment bending, Curvature-tool anchor moves, cutting at anchors (Scissors), Join, Average,\n * Remove Redundant Points and Smooth.\n *\n * Portions ported from (MIT OR Apache-2.0, © ArtCraft Team and contributors; see\n * THIRD_PARTY_NOTICES.md):\n * - vectorcraft-main/crates/pathops/src/edit.rs (`remove_anchor`, `golden_min`, `smooth`,\n * `remove_redundant_points`, `merge_pair`, `average`, `join`)\n * - vectorcraft-main/crates/geom/src/path.rs (`SubPath::cut_at`)\n * - vectorcraft-main/crates/tools/src/draw2/curvature.rs (`curvature_move`)\n * - figcraft-main/crates/engine/src/vectoredit/ops.rs (`bend`)\n */\nimport type { Point } from '../canvas/types.js';\nimport { fitCubicWithTangents } from '../canvas/curveFit.js';\nimport { segmentPoint } from '../canvas/pathops/bezier.js';\nimport { cloneAnchor, inferAnchorType, insertAnchor, moveAnchorHandle, subpathSegment, subpathSegmentCount } from './geometry.js';\nimport type { VectorAnchor, VectorSubpath, Vec2 } from './types.js';\n\nconst EPS = 1e-9;\n\n// ---------------------------------------------------------------------------\n// Small helpers\n// ---------------------------------------------------------------------------\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/** Segment `i` as four control points (a line has its controls at the thirds of its chord). */\nfunction cubicOf(sp: VectorSubpath, i: number): Point[] {\n\tconst s = subpathSegment(sp, i);\n\tif (s.length === 4) return s;\n\tconst [a, b] = s;\n\treturn [a, [a[0] + (b[0] - a[0]) / 3, a[1] + (b[1] - a[1]) / 3], [a[0] + (2 * (b[0] - a[0])) / 3, a[1] + (2 * (b[1] - a[1])) / 3], b];\n}\n\nconst isLine = (sp: VectorSubpath, i: number) => subpathSegment(sp, i).length === 2;\n\n/** Direction the curve leaves `c[0]`. */\nfunction startTangent(c: readonly Point[]): Point {\n\tfor (let k = 1; k < 4; k++) {\n\t\tconst u = unit(c[k][0] - c[0][0], c[k][1] - c[0][1]);\n\t\tif (u) return u;\n\t}\n\treturn [1, 0];\n}\n\n/** Direction the curve arrives at `c[3]` (forward along the curve). */\nfunction endTangent(c: readonly Point[]): Point {\n\tfor (let k = 2; k >= 0; k--) {\n\t\tconst u = unit(c[3][0] - c[k][0], c[3][1] - c[k][1]);\n\t\tif (u) return u;\n\t}\n\treturn [1, 0];\n}\n\n/** `n + 1` points of a cubic at even parameters (`withStart` false skips the first). */\nfunction sample(c: readonly Point[], n: number, out: Point[], withStart: boolean) {\n\tfor (let i = withStart ? 0 : 1; i <= n; i++) out.push(segmentPoint(c, i / n));\n}\n\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/** Drops handles that sit on their anchor and sets the type the handles show (a symmetric pair that is no longer equal becomes smooth). */\nfunction tidy(a: VectorAnchor, keepType = true): VectorAnchor {\n\tconst next = cloneAnchor(a);\n\tif (onAnchor(next, next.in)) delete next.in;\n\tif (onAnchor(next, next.out)) delete next.out;\n\tconst shown = inferAnchorType(next);\n\tconst type = keepType && a.type ? (a.type === 'symmetric' && shown !== 'symmetric' ? (shown === 'corner' ? 'corner' : 'smooth') : a.type === 'smooth' && shown === 'corner' ? 'corner' : a.type) : shown;\n\tif (type === 'corner') delete next.type;\n\telse next.type = type;\n\treturn next;\n}\n\n/** Index of the anchor before / after `i` (round the start of a closed subpath), or -1. */\nfunction neighbours(sp: VectorSubpath, i: number): [number, number] {\n\tconst n = sp.anchors.length;\n\tconst wrap = sp.closed && n > 1;\n\treturn [i > 0 ? i - 1 : wrap ? n - 1 : -1, i < n - 1 ? i + 1 : wrap ? 0 : -1];\n}\n\n// ---------------------------------------------------------------------------\n// Deleting anchors without changing the shape\n// ---------------------------------------------------------------------------\n\n/**\n * The value of `f` with the least error for an argument in [a, b], by a golden-section search\n * (a fixed number of steps). An error that is not a number never wins.\n */\nfunction goldenMin<T extends { error: number }>(f: (x: number) => T, a: number, b: number): T {\n\tconst R = 0.618033988749895;\n\tlet x1 = b - R * (b - a);\n\tlet x2 = a + R * (b - a);\n\tlet f1 = f(x1);\n\tlet f2 = f(x2);\n\tfor (let i = 0; i < 40; i++) {\n\t\tif (f1.error < f2.error || Number.isNaN(f2.error)) {\n\t\t\tb = x2;\n\t\t\tx2 = x1;\n\t\t\tf2 = f1;\n\t\t\tx1 = b - R * (b - a);\n\t\t\tf1 = f(x1);\n\t\t} else {\n\t\t\ta = x1;\n\t\t\tx1 = x2;\n\t\t\tf1 = f2;\n\t\t\tx2 = a + R * (b - a);\n\t\t\tf2 = f(x2);\n\t\t}\n\t}\n\treturn f1.error <= f2.error ? f1 : f2;\n}\n\n/**\n * One cubic through `pts`: `N + 1` samples of a left curve then `N` of a right one. The new\n * curve passes their joint at some parameter k: the left samples sit at k·i/N, the right ones\n * at k + (1 − k)·i/N. Searches k for the closest fit.\n */\nfunction fitAcross(pts: Point[], N: number, t0: Point, t1: Point) {\n\tconst fit = (k: number) => {\n\t\tconst u: number[] = [];\n\t\tfor (let i = 0; i <= N; i++) u.push((k * i) / N);\n\t\tfor (let i = 1; i <= N; i++) u.push(k + ((1 - k) * i) / N);\n\t\treturn fitCubicWithTangents(pts, u, t0, t1);\n\t};\n\treturn goldenMin(fit, 0, 1);\n}\n\n/**\n * Removes anchor `index` without opening the path: its neighbours keep their handle directions\n * and their facing handles are refitted so one cubic follows the two segments that met there\n * as closely as possible (least maximum error over where the new curve passes the removed\n * point). Two straight segments become one; removing an end of an open subpath drops its\n * segment. Returns the subpath unchanged (a copy) when `index` is out of range.\n */\nexport function removeAnchorKeepingShape(sp: VectorSubpath, index: number): VectorSubpath {\n\tconst anchors = sp.anchors.map(cloneAnchor);\n\tconst n = anchors.length;\n\tif (index < 0 || index >= n) return { anchors, closed: sp.closed };\n\tif ((sp.closed || (index > 0 && index < n - 1)) && n >= 3) {\n\t\tconst pi = (index + n - 1) % n;\n\t\tconst ni = (index + 1) % n;\n\t\tif (!(isLine(sp, pi) && isLine(sp, index))) {\n\t\t\tconst left = cubicOf(sp, pi);\n\t\t\tconst right = cubicOf(sp, index);\n\t\t\tconst N = 16;\n\t\t\tconst pts: Point[] = [];\n\t\t\tsample(left, N, pts, true);\n\t\t\tsample(right, N, pts, false);\n\t\t\tconst t0 = startTangent(left);\n\t\t\tconst t1 = endTangent(right);\n\t\t\tconst { curve } = fitAcross(pts, N, t0, t1);\n\t\t\tif ([curve.p1, curve.p2].every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]))) {\n\t\t\t\tanchors[pi] = tidy({ ...anchors[pi], out: { x: curve.p1[0], y: curve.p1[1] } });\n\t\t\t\tanchors[ni] = tidy({ ...anchors[ni], in: { x: curve.p2[0], y: curve.p2[1] } });\n\t\t\t}\n\t\t}\n\t}\n\tanchors.splice(index, 1);\n\tlet closed = sp.closed;\n\tif (!closed && anchors.length) {\n\t\t// A new end has nothing beyond it.\n\t\tanchors[0] = tidy({ ...anchors[0], in: null });\n\t\tconst last = anchors.length - 1;\n\t\tanchors[last] = tidy({ ...anchors[last], out: null });\n\t}\n\tif (anchors.length < 3) closed = closed && anchors.length === 2 && anchors.some((a) => a.in || a.out);\n\treturn { anchors, closed };\n}\n\n/** Removes several anchors of a subpath, keeping the shape (see `removeAnchorKeepingShape`). */\nexport function removeAnchorsKeepingShape(sp: VectorSubpath, indices: readonly number[]): VectorSubpath {\n\tconst order = [...new Set(indices)].filter((i) => i >= 0 && i < sp.anchors.length).sort((a, b) => b - a);\n\treturn order.reduce((acc, i) => removeAnchorKeepingShape(acc, i), { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed });\n}\n\n// ---------------------------------------------------------------------------\n// Bending a segment\n// ---------------------------------------------------------------------------\n\n/**\n * Bends segment `segment` so the curve passes through `p` at parameter `t` (clamped to\n * 0.05–0.95): its handles go to the thirds of the chord plus one shared offset, so\n * `B(t) = L(t) + 3t(1 − t)·offset` exactly. The end anchors keep their types (a smooth or\n * symmetric end turns its other handle to stay in line).\n */\nexport function bendSegment(sp: VectorSubpath, segment: number, t: number, p: Vec2): VectorSubpath {\n\tconst anchors = sp.anchors.map(cloneAnchor);\n\tconst n = anchors.length;\n\tif (segment < 0 || segment >= subpathSegmentCount(sp) || !Number.isFinite(t) || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return { anchors, closed: sp.closed };\n\tconst tc = Math.min(0.95, Math.max(0.05, t));\n\tconst ia = segment;\n\tconst ib = (segment + 1) % n;\n\tconst a = anchors[ia];\n\tconst b = anchors[ib];\n\tconst k = 3 * tc * (1 - tc);\n\tconst ox = (p.x - (a.x + (b.x - a.x) * tc)) / k;\n\tconst oy = (p.y - (a.y + (b.y - a.y) * tc)) / k;\n\tconst c1 = { x: a.x + (b.x - a.x) / 3 + ox, y: a.y + (b.y - a.y) / 3 + oy };\n\tconst c2 = { x: b.x + (a.x - b.x) / 3 + ox, y: b.y + (a.y - b.y) / 3 + oy };\n\tanchors[ia] = moveAnchorHandle(a, 'out', c1);\n\tanchors[ib] = moveAnchorHandle(b, 'in', c2);\n\treturn { anchors, closed: sp.closed };\n}\n\n/** The point at parameter `t` of segment `segment` (for tests and previews). */\nexport function subpathPointAt(sp: VectorSubpath, segment: number, t: number): Vec2 {\n\tconst [x, y] = segmentPoint(subpathSegment(sp, segment), t);\n\treturn { x, y };\n}\n\n// ---------------------------------------------------------------------------\n// Curvature-tool anchor move\n// ---------------------------------------------------------------------------\n\n/**\n * Moves anchor `index` to `q` the way the Curvature tool drags a point: only the two segments\n * at it change. A smooth or symmetric anchor turns its handles along the line through its\n * neighbours; the neighbours' handles on those segments, and a corner's own handles, keep\n * their directions while their lengths scale with their segment's chord, so the segments\n * beyond don't move and a corner without handles keeps none.\n */\nexport function curvatureMoveAnchor(sp: VectorSubpath, index: number, q: Vec2): VectorSubpath {\n\tconst anchors = sp.anchors.map(cloneAnchor);\n\tconst a = sp.anchors[index];\n\tif (!a) return { anchors, closed: sp.closed };\n\tconst own: [Vec2 | null, Vec2 | null] = [\n\t\ta.in && !onAnchor(a, a.in) ? { x: a.in.x - a.x, y: a.in.y - a.y } : null,\n\t\ta.out && !onAnchor(a, a.out) ? { x: a.out.x - a.x, y: a.out.y - a.y } : null\n\t];\n\tconst ends: [Vec2 | null, Vec2 | null] = [null, null];\n\tneighbours(sp, index).forEach((j, side) => {\n\t\tif (j < 0 || j === index) return;\n\t\tconst b = anchors[j];\n\t\tconst old = Math.hypot(a.x - b.x, a.y - b.y);\n\t\tconst s = old > 1e-9 ? Math.hypot(q.x - b.x, q.y - b.y) / old : 1;\n\t\tconst key = side === 0 ? 'out' : 'in';\n\t\tconst h = b[key];\n\t\tif (h) b[key] = { x: b.x + (h.x - b.x) * s, y: b.y + (h.y - b.y) * s };\n\t\tconst o = own[side];\n\t\tif (o) own[side] = { x: o.x * s, y: o.y * s };\n\t\tends[side] = { x: b.x, y: b.y };\n\t});\n\tconst moved: VectorAnchor = { x: q.x, y: q.y };\n\tif (own[0]) moved.in = { x: q.x + own[0].x, y: q.y + own[0].y };\n\tif (own[1]) moved.out = { x: q.x + own[1].x, y: q.y + own[1].y };\n\tif (a.type) moved.type = a.type;\n\tif (a.corner) moved.corner = { ...a.corner };\n\tconst [prev, next] = ends;\n\tif (a.type && a.type !== 'corner' && prev && next) {\n\t\tconst dx = next.x - prev.x;\n\t\tconst dy = next.y - prev.y;\n\t\tconst l = Math.hypot(dx, dy);\n\t\tif (l > 1e-9) {\n\t\t\t// A handle it lacks comes out a third of the way to that side's neighbour.\n\t\t\tconst len = (h: Vec2 | null, to: Vec2) => (h ? Math.hypot(h.x, h.y) : Math.hypot(q.x - to.x, q.y - to.y) / 3);\n\t\t\tlet li = len(own[0], prev);\n\t\t\tlet lo = len(own[1], next);\n\t\t\tif (a.type === 'symmetric') li = lo = (li + lo) / 2;\n\t\t\tmoved.in = { x: q.x - (dx * li) / l, y: q.y - (dy * li) / l };\n\t\t\tmoved.out = { x: q.x + (dx * lo) / l, y: q.y + (dy * lo) / l };\n\t\t}\n\t}\n\tanchors[index] = moved;\n\treturn { anchors, closed: sp.closed };\n}\n\n// ---------------------------------------------------------------------------\n// Scissors\n// ---------------------------------------------------------------------------\n\n/**\n * Cuts a subpath at the anchors `at`: each becomes the end of one open piece and the start of\n * the next, its handle off each piece dropped (the shape doesn't change). A closed subpath\n * opens at its first cut, so one cut leaves one piece whose ends coincide. The ends of an open\n * subpath and indices out of range don't cut; with no cut left the subpath comes back as it is.\n */\nexport function cutSubpathAt(sp: VectorSubpath, at: readonly number[]): VectorSubpath[] {\n\tconst n = sp.anchors.length;\n\tconst cuts = [...new Set(at)].filter((i) => Number.isInteger(i) && i >= 0 && i < n && (sp.closed || (i > 0 && i < n - 1))).sort((a, b) => a - b);\n\tif (!cuts.length || n < 2) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];\n\tconst first = cuts[0];\n\t// A closed subpath's anchors from the first cut round to it again; an open one's as they are.\n\tconst run = sp.closed ? Array.from({ length: n + 1 }, (_, i) => sp.anchors[(first + i) % n]) : sp.anchors;\n\tconst cutSet = new Set(sp.closed ? [...cuts.map((c) => (c - first + n) % n), n] : cuts);\n\tconst pieces: VectorSubpath[] = [];\n\tlet cur: VectorAnchor[] = [];\n\trun.forEach((a, i) => {\n\t\tif (!cutSet.has(i)) {\n\t\t\tcur.push(cloneAnchor(a));\n\t\t\treturn;\n\t\t}\n\t\tif (cur.length) {\n\t\t\tcur.push(tidy({ ...a, out: null }, false));\n\t\t\tpieces.push({ anchors: cur, closed: false });\n\t\t\tcur = [];\n\t\t}\n\t\tif (i < n) cur.push(tidy({ ...a, in: null }, false));\n\t});\n\tif (cur.length > 1) pieces.push({ anchors: cur, closed: false });\n\treturn pieces;\n}\n\n/**\n * Cuts a subpath at parameter `t` of segment `segment` (Scissors on a segment): an anchor is\n * inserted there without changing the shape, then cut. Within `endTolerance` of the segment's\n * ends (in parameter) the existing anchor is cut instead.\n */\nexport function splitSubpathAt(sp: VectorSubpath, segment: number, t: number, endTolerance = 1e-6): VectorSubpath[] {\n\tconst n = sp.anchors.length;\n\tif (segment < 0 || segment >= subpathSegmentCount(sp)) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];\n\tif (t <= endTolerance) return cutSubpathAt(sp, [segment]);\n\tif (t >= 1 - endTolerance) return cutSubpathAt(sp, [(segment + 1) % n]);\n\tconst { subpath, index } = insertAnchor(sp, segment, t);\n\treturn cutSubpathAt(subpath, [index]);\n}\n\n// ---------------------------------------------------------------------------\n// Join\n// ---------------------------------------------------------------------------\n\nconst endOf = (sp: VectorSubpath, last: boolean): VectorAnchor => sp.anchors[last ? sp.anchors.length - 1 : 0];\nconst dist = (a: Vec2, b: Vec2) => Math.hypot(a.x - b.x, a.y - b.y);\n\nfunction reverse(sp: VectorSubpath): VectorSubpath {\n\treturn {\n\t\tclosed: sp.closed,\n\t\tanchors: sp.anchors\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.map((a) => {\n\t\t\t\tconst next: VectorAnchor = { x: a.x, y: a.y };\n\t\t\t\tif (a.out) next.in = { ...a.out };\n\t\t\t\tif (a.in) next.out = { ...a.in };\n\t\t\t\tif (a.type) next.type = a.type;\n\t\t\t\tif (a.corner) next.corner = { ...a.corner };\n\t\t\t\treturn next;\n\t\t\t})\n\t};\n}\n\n/**\n * Join: open subpaths are joined nearest-endpoints-first into one open subpath (endpoints\n * within `tolerance` merge into one anchor, otherwise a straight segment connects them). A\n * single open subpath is closed instead (its ends merged when they meet). Closed subpaths and\n * lone anchors pass through unchanged, before the joined one.\n */\nexport function joinSubpaths(subpaths: readonly VectorSubpath[], tolerance: number): VectorSubpath[] {\n\tconst closed: VectorSubpath[] = [];\n\tlet open: VectorSubpath[] = [];\n\tfor (const sp of subpaths) {\n\t\tconst copy = { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };\n\t\tif (sp.closed || sp.anchors.length < 2) {\n\t\t\tif (sp.anchors.length) closed.push(copy);\n\t\t} else open.push(copy);\n\t}\n\tif (open.length === 1) {\n\t\tconst sp = open[0];\n\t\tconst first = sp.anchors[0];\n\t\tconst last = sp.anchors[sp.anchors.length - 1];\n\t\tif (sp.anchors.length > 2 && dist(first, last) <= tolerance) {\n\t\t\tsp.anchors.pop();\n\t\t\tsp.anchors[0] = tidy({ ...first, in: last.in ?? null }, false);\n\t\t} else {\n\t\t\tsp.anchors[0] = tidy({ ...first, in: null }, false);\n\t\t\tsp.anchors[sp.anchors.length - 1] = tidy({ ...sp.anchors[sp.anchors.length - 1], out: null }, false);\n\t\t}\n\t\tsp.closed = true;\n\t\treturn [...closed, sp];\n\t}\n\twhile (open.length > 1) {\n\t\t// The closest pair of endpoints on different subpaths.\n\t\tlet best = { d: Infinity, i: 0, j: 1, ei: true, ej: false };\n\t\tfor (let i = 0; i < open.length; i++) {\n\t\t\tfor (let j = i + 1; j < open.length; j++) {\n\t\t\t\tfor (const ei of [false, true]) {\n\t\t\t\t\tfor (const ej of [false, true]) {\n\t\t\t\t\t\tconst d = dist(endOf(open[i], ei), endOf(open[j], ej));\n\t\t\t\t\t\tif (d < best.d) best = { d, i, j, ei, ej };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlet a = open[best.i];\n\t\tlet b = open[best.j];\n\t\topen = open.filter((_, k) => k !== best.j);\n\t\t// `a` ends at the meeting point, `b` starts there.\n\t\tif (!best.ei) a = reverse(a);\n\t\tif (best.ej) b = reverse(b);\n\t\tconst anchors = a.anchors;\n\t\tif (best.d <= tolerance) {\n\t\t\tconst [head, ...rest] = b.anchors;\n\t\t\tconst l = anchors[anchors.length - 1];\n\t\t\tanchors[anchors.length - 1] = tidy({ ...l, out: head.out ?? null }, false);\n\t\t\tanchors.push(...rest);\n\t\t} else {\n\t\t\tconst l = anchors[anchors.length - 1];\n\t\t\tanchors[anchors.length - 1] = tidy({ ...l, out: null }, false);\n\t\t\tanchors.push(tidy({ ...b.anchors[0], in: null }, false), ...b.anchors.slice(1));\n\t\t}\n\t\topen[best.i > best.j ? best.i - 1 : best.i] = { anchors, closed: false };\n\t}\n\treturn [...closed, ...open];\n}\n\n// ---------------------------------------------------------------------------\n// Smooth and Remove Redundant Points\n// ---------------------------------------------------------------------------\n\n/**\n * Smooth: pulls handles towards Catmull-Rom-like tangents (a third of the way to each\n * neighbour along the line through both). `amount` 0–1 (0 = unchanged, 1 = fully smooth).\n * Only the anchors in `only` change when it is given; endpoints of open subpaths keep their\n * handles. Subpaths of fewer than three anchors are unchanged.\n */\nexport function smoothSubpath(sp: VectorSubpath, amount = 1, only?: ReadonlySet<number>): VectorSubpath {\n\tconst t = Math.min(1, Math.max(0, amount));\n\tconst orig = sp.anchors;\n\tconst n = orig.length;\n\tconst anchors = orig.map(cloneAnchor);\n\tif (n < 3 || t === 0) return { anchors, closed: sp.closed };\n\tfor (let i = 0; i < n; i++) {\n\t\tif (!sp.closed && (i === 0 || i === n - 1)) continue;\n\t\tif (only && !only.has(i)) continue;\n\t\tconst prev = orig[(i + n - 1) % n];\n\t\tconst next = orig[(i + 1) % n];\n\t\tconst p = orig[i];\n\t\tconst dir = unit(next.x - prev.x, next.y - prev.y);\n\t\tif (!dir) continue;\n\t\tconst li = dist(p, prev) / 3;\n\t\tconst lo = dist(p, next) / 3;\n\t\tconst hin = p.in ?? p;\n\t\tconst hout = p.out ?? p;\n\t\tconst target = (h: Vec2, x: number, y: number) => ({ x: h.x + (x - h.x) * t, y: h.y + (y - h.y) * t });\n\t\tanchors[i] = tidy(\n\t\t\t{\n\t\t\t\tx: p.x,\n\t\t\t\ty: p.y,\n\t\t\t\tin: target(hin, p.x - dir[0] * li, p.y - dir[1] * li),\n\t\t\t\tout: target(hout, p.x + dir[0] * lo, p.y + dir[1] * lo)\n\t\t\t},\n\t\t\tfalse\n\t\t);\n\t}\n\treturn { anchors, closed: sp.closed };\n}\n\ninterface Seg {\n\tc: Point[];\n\tline: boolean;\n}\n\nfunction segsOf(sp: VectorSubpath): Seg[] {\n\tconst out: Seg[] = [];\n\tfor (let i = 0; i < subpathSegmentCount(sp); i++) {\n\t\tconst s = subpathSegment(sp, i);\n\t\tout.push(s.length === 2 ? { c: s, line: true } : { c: s, line: false });\n\t}\n\treturn out;\n}\n\n/** A subpath from consecutive segments (anchor types inferred; original types kept where they still hold). */\nfunction segsToSubpath(segs: readonly Seg[], closed: boolean, types: Map<string, VectorAnchor['type']>): VectorSubpath | null {\n\tif (!segs.length) return null;\n\tconst anchors: VectorAnchor[] = [];\n\tconst at = (p: Point): VectorAnchor => ({ x: p[0], y: p[1] });\n\tanchors.push(at(segs[0].c[0]));\n\tfor (const s of segs) {\n\t\tconst end = s.c[s.c.length - 1];\n\t\tif (!s.line) anchors[anchors.length - 1].out = { x: s.c[1][0], y: s.c[1][1] };\n\t\tconst next = at(end);\n\t\tif (!s.line) next.in = { x: s.c[2][0], y: s.c[2][1] };\n\t\tanchors.push(next);\n\t}\n\tif (closed) {\n\t\tconst last = anchors.pop()!;\n\t\tif (last.in) anchors[0].in = last.in;\n\t}\n\tconst typed = anchors.map((a) => {\n\t\tconst type = types.get(`${a.x},${a.y}`);\n\t\treturn tidy(type ? { ...a, type } : a, !!type);\n\t});\n\treturn { anchors: typed, closed };\n}\n\nconst isZeroSeg = (s: Seg) => s.c.every((p) => Math.abs(p[0] - s.c[0][0]) < 1e-9 && Math.abs(p[1] - s.c[0][1]) < 1e-9);\n\n/** One segment replacing `a` then `b` within `tol`, or null. */\nfunction mergePair(a: Seg, b: Seg, tol: number): Seg | null {\n\tconst a0 = a.c[0];\n\tconst a3 = a.c[a.c.length - 1];\n\tconst b3 = b.c[b.c.length - 1];\n\tif (a.line && b.line) {\n\t\tconst cx = b3[0] - a0[0];\n\t\tconst cy = b3[1] - a0[1];\n\t\tconst len = Math.hypot(cx, cy);\n\t\tif (len < 1e-12) return null;\n\t\tconst mx = a3[0] - a0[0];\n\t\tconst my = a3[1] - a0[1];\n\t\tconst along = (mx * cx + my * cy) / len;\n\t\treturn Math.abs((mx * cy - my * cx) / len) <= tol && along >= 0 && along <= len ? { c: [a0, b3], line: true } : null;\n\t}\n\tconst ca = a.line ? [a0, a0, a3, a3] : a.c;\n\tconst cb = b.line ? [a3, a3, b3, b3] : b.c;\n\tconst ta = endTangent(ca);\n\tconst tb = startTangent(cb);\n\t// Only merge across (near-)smooth joints.\n\tif (ta[0] * tb[0] + ta[1] * tb[1] < 0.99) return null;\n\tconst pts: Point[] = [];\n\tsample(ca, 16, pts, true);\n\tsample(cb, 16, pts, false);\n\tconst { curve, error } = fitAcross(pts, 16, startTangent(ca), endTangent(cb));\n\treturn error <= tol ? { c: [curve.p0, curve.p1, curve.p2, curve.p3], line: false } : null;\n}\n\n/**\n * Remove Redundant Points: drops anchors that can go without changing the shape by more than\n * `tolerance` (collinear corners, duplicate points, and smooth joints that one cubic can\n * replace).\n */\nexport function removeRedundantPoints(sp: VectorSubpath, tolerance: number): VectorSubpath {\n\tconst tol = Math.max(tolerance, 1e-9);\n\tconst types = new Map(sp.anchors.filter((a) => a.type).map((a) => [`${a.x},${a.y}`, a.type] as const));\n\tconst segs = segsOf(sp).filter((s) => !isZeroSeg(s));\n\tif (!segs.length) return { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };\n\tconst minSegs = sp.closed ? 2 : 1;\n\tlet changed = true;\n\twhile (changed && segs.length > minSegs) {\n\t\tchanged = false;\n\t\tconst n = segs.length;\n\t\tconst joints = sp.closed ? n : n - 1;\n\t\tfor (let j = 0; j < joints; j++) {\n\t\t\tconst [i0, i1] = sp.closed ? [(j + n - 1) % n, j] : [j, j + 1];\n\t\t\tconst m = mergePair(segs[i0], segs[i1], tol);\n\t\t\tif (!m) continue;\n\t\t\tsegs[i0] = m;\n\t\t\tsegs.splice(i1, 1);\n\t\t\t// The merged segment now starts the loop.\n\t\t\tif (sp.closed && i1 === 0) segs.unshift(segs.pop()!);\n\t\t\tchanged = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn segsToSubpath(segs, sp.closed, types) ?? { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };\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.\n */\nexport function eraseFromOutline(sp: VectorSubpath, region: BezierPathInput): 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 removed = (p: Point) => pointInBezierPath(region, p, 'nonzero');\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 * Pencil helpers: lazy-mouse smoothing (a pulled string between the pointer and the pen) and\n * ink-to-shape recognition (a hand-drawn stroke that is a line, rectangle, ellipse or polygon).\n *\n * Portions ported from (MIT OR Apache-2.0, © ArtCraft Team and contributors; see\n * THIRD_PARTY_NOTICES.md):\n * - photocraft-main/crates/paint/src/dynamics.rs (`Smoother`: pulled-string mode, `string`,\n * `finish` with catch-up on stroke end)\n * - wordcraft-main/crates/engine/src/cmd/ink_shape.rs (`recognise`, `ellipse_rms`,\n * `snap_square`, `corners`, `turning`, `rdp`, `upright`)\n */\nimport type { Point } from '../canvas/types.js';\n\n// ---------------------------------------------------------------------------\n// Lazy mouse\n// ---------------------------------------------------------------------------\n\n/**\n * Pulled-string smoothing: the pen stays put until the pointer is more than `length` away,\n * then follows it at that distance, so small wobbles never reach the stroke. Feed pointer\n * positions to `push`; it returns the pen positions to draw through (possibly none).\n */\nexport class LazyMouse {\n\tprivate pos: Point | null = null;\n\tprivate last: Point | null = null;\n\n\t/** `length` is the string length in the units of the points (0 turns smoothing off). */\n\tconstructor(readonly length: number) {}\n\n\tpush(p: Point): Point[] {\n\t\tthis.last = p;\n\t\tif (!(this.length > 0)) return [p];\n\t\tconst pos = this.pos;\n\t\tif (!pos) {\n\t\t\tthis.pos = p;\n\t\t\treturn [p];\n\t\t}\n\t\tconst dx = p[0] - pos[0];\n\t\tconst dy = p[1] - pos[1];\n\t\tconst d = Math.hypot(dx, dy);\n\t\tif (d <= this.length) return [];\n\t\tconst k = (d - this.length) / d;\n\t\tconst next: Point = [pos[0] + dx * k, pos[1] + dy * k];\n\t\tthis.pos = next;\n\t\treturn [next];\n\t}\n\n\t/** The string from the pen to the pointer while they are apart, else null. */\n\tstring(): [Point, Point] | null {\n\t\tconst { pos, last } = this;\n\t\tif (!(this.length > 0) || !pos || !last || (pos[0] === last[0] && pos[1] === last[1])) return null;\n\t\treturn [pos, last];\n\t}\n\n\t/** End of stroke: the pen catches up with the pointer (the point to add, if it lagged). */\n\tfinish(): Point[] {\n\t\tconst { pos, last } = this;\n\t\tif (!(this.length > 0) || !pos || !last || (pos[0] === last[0] && pos[1] === last[1])) return [];\n\t\tthis.pos = last;\n\t\treturn [last];\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Ink to shape\n// ---------------------------------------------------------------------------\n\n/** A shape recognised in a stroke, in the stroke's coordinates. */\nexport type VectorInkShape =\n\t| { kind: 'line'; a: Point; b: Point }\n\t| { kind: 'rect'; x: number; y: number; width: number; height: number }\n\t| { kind: 'ellipse'; x: number; y: number; width: number; height: number }\n\t| { kind: 'polygon'; points: Point[] };\n\nexport interface RecognizeInkOptions {\n\t/** Strokes whose longer side is smaller than this stay ink (default 6). */\n\tminSize?: number;\n}\n\n/** Most corners a recognised polygon may have. */\nexport const VECTOR_INK_MAX_CORNERS = 8;\nconst MAX_POINTS = 100_000;\n\n/** \"line\", \"square\", \"rectangle\", \"circle\", \"ellipse\", \"triangle\" or \"polygon\". */\nexport function vectorInkShapeName(shape: VectorInkShape): string {\n\tswitch (shape.kind) {\n\t\tcase 'line':\n\t\t\treturn 'line';\n\t\tcase 'rect':\n\t\t\treturn shape.width === shape.height ? 'square' : 'rectangle';\n\t\tcase 'ellipse':\n\t\t\treturn shape.width === shape.height ? 'circle' : 'ellipse';\n\t\tcase 'polygon':\n\t\t\treturn shape.points.length === 3 ? 'triangle' : 'polygon';\n\t}\n}\n\nconst dist = (a: Point, b: Point) => Math.hypot(b[0] - a[0], b[1] - a[1]);\n\nfunction bbox(pts: readonly Point[]): [number, number, number, number] {\n\tif (!pts.length) return [0, 0, 0, 0];\n\tlet [x0, y0] = pts[0];\n\tlet [x1, y1] = pts[0];\n\tfor (const [x, y] of pts) {\n\t\tx0 = Math.min(x0, x);\n\t\ty0 = Math.min(y0, y);\n\t\tx1 = Math.max(x1, x);\n\t\ty1 = Math.max(y1, y);\n\t}\n\treturn [x0, y0, x1 - x0, y1 - y0];\n}\n\nfunction segDistance(a: Point, b: Point, p: Point): number {\n\tconst dx = b[0] - a[0];\n\tconst dy = b[1] - a[1];\n\tconst len2 = dx * dx + dy * dy;\n\tconst t = len2 > 0 ? Math.min(1, Math.max(0, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2)) : 0;\n\treturn dist([a[0] + t * dx, a[1] + t * dy], p);\n}\n\nfunction polygonDistance(c: readonly Point[], p: Point): number {\n\tlet best = Infinity;\n\tfor (let i = 0; i < c.length; i++) best = Math.min(best, segDistance(c[i], c[(i + 1) % c.length], p));\n\treturn best;\n}\n\n/** RMS of each point's normalised radius minus one on the ellipse inscribed in the box (0 on it). */\nfunction ellipseRms(p: readonly Point[], x0: number, y0: number, w: number, h: number): number {\n\tconst rx = w / 2;\n\tconst ry = h / 2;\n\tif (rx <= 0 || ry <= 0 || !p.length) return Infinity;\n\tconst cx = x0 + rx;\n\tconst cy = y0 + ry;\n\tlet sum = 0;\n\tfor (const [x, y] of p) sum += (Math.hypot((x - cx) / rx, (y - cy) / ry) - 1) ** 2;\n\treturn Math.sqrt(sum / p.length);\n}\n\n/** A box drawn nearly square made square (same centre). */\nfunction snapSquare(x: number, y: number, w: number, h: number): [number, number, number, number] {\n\tif (Math.abs(w - h) <= 0.12 * Math.max(w, h)) {\n\t\tconst s = (w + h) / 2;\n\t\treturn [x + (w - s) / 2, y + (h - s) / 2, s, s];\n\t}\n\treturn [x, y, w, h];\n}\n\n/** How far the path turns at `b` coming from `a` towards `c`, in degrees (0 = straight on). */\nfunction turning(a: Point, b: Point, c: Point): number {\n\tconst u = [b[0] - a[0], b[1] - a[1]];\n\tconst v = [c[0] - b[0], c[1] - b[1]];\n\tconst lu = Math.hypot(u[0], u[1]);\n\tconst lv = Math.hypot(v[0], v[1]);\n\tif (lu <= 0 || lv <= 0) return 0;\n\treturn (Math.acos(Math.min(1, Math.max(-1, (u[0] * v[0] + u[1] * v[1]) / (lu * lv)))) * 180) / Math.PI;\n}\n\n/** Ramer–Douglas–Peucker reduction of an open polyline (iterative), keeping both ends. */\nfunction rdp(p: readonly Point[], eps: number): Point[] {\n\tconst n = p.length;\n\tif (n < 3) return p.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\tlet far = a;\n\t\tlet farD = 0;\n\t\tfor (let i = a + 1; i < b; i++) {\n\t\t\tconst d = segDistance(p[a], p[b], p[i]);\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 (farD > eps) {\n\t\t\tkeep[far] = true;\n\t\t\tstack.push([a, far], [far, b]);\n\t\t}\n\t}\n\treturn p.filter((_, i) => keep[i]);\n}\n\n/** Corners of the closed path through `p`: RDP split at the point farthest from the start, then flat and crowded corners merged away. */\nfunction corners(p: readonly Point[], diag: number): Point[] {\n\tconst eps = 0.05 * diag;\n\tconst start = p[0];\n\tlet far = 0;\n\tlet farD = 0;\n\tp.forEach((q, i) => {\n\t\tconst d = dist(start, q);\n\t\tif (d > farD) {\n\t\t\tfarD = d;\n\t\t\tfar = i;\n\t\t}\n\t});\n\tconst a = p.slice(0, far);\n\tconst b = p.slice(far);\n\tconst ring = rdp([...a, ...b.slice(0, 1)], eps);\n\tconst back = rdp([...b, start], eps);\n\t// The halves share the far point and the start.\n\tring.pop();\n\tback.pop();\n\tring.push(...back);\n\twhile (ring.length > 3) {\n\t\tconst n = ring.length;\n\t\tconst turn = (i: number) => turning(ring[(i + n - 1) % n], ring[i], ring[(i + 1) % n]);\n\t\tlet flat = 0;\n\t\tlet flatT = Infinity;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst t = turn(i);\n\t\t\tif (t < flatT) {\n\t\t\t\tflatT = t;\n\t\t\t\tflat = i;\n\t\t\t}\n\t\t}\n\t\tlet short = -1;\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\tif (dist(ring[j], ring[(j + 1) % n]) < 0.1 * diag) {\n\t\t\t\tshort = j;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (flatT < 30) ring.splice(flat, 1);\n\t\telse if (short >= 0) {\n\t\t\t// Of the two crowded corners keep the sharper.\n\t\t\tconst k = turn(short) < turn((short + 1) % n) ? short : (short + 1) % n;\n\t\t\tring.splice(k, 1);\n\t\t} else break;\n\t}\n\treturn ring;\n}\n\n/** A quadrilateral whose sides run (nearly) across and down, alternately. */\nfunction upright(c: readonly Point[]): boolean {\n\tconst n = c.length;\n\tconst dirs = c.map((a, i) => {\n\t\tconst b = c[(i + 1) % n];\n\t\tconst deg = ((((Math.atan2(b[1] - a[1], b[0] - a[0]) * 180) / Math.PI) % 180) + 180) % 180;\n\t\tif (deg <= 15 || deg >= 165) return 'across';\n\t\tif (deg >= 75 && deg <= 105) return 'down';\n\t\treturn null;\n\t});\n\treturn dirs.every((d) => d !== null) && dirs.every((d, i) => d !== dirs[(i + 1) % n]);\n}\n\n/**\n * The shape a hand-drawn stroke draws, if it is one: an open stroke is a line when it stays\n * close to the chord between its ends; a closed one is an ellipse when its points sit on the\n * ellipse inscribed in their bounds, else a polygon of 3–8 corners when that polygon stays\n * close to every point (an upright four-cornered one is a rectangle). Nearly square boxes\n * become squares and circles. Anything else (scribbles, curves, letters) is null.\n */\nexport function recognizeInkShape(points: readonly Point[], options: RecognizeInkOptions = {}): VectorInkShape | null {\n\tconst minSize = options.minSize ?? 6;\n\tconst p: Point[] = [];\n\tfor (const q of points.slice(0, MAX_POINTS)) {\n\t\tif (!Number.isFinite(q[0]) || !Number.isFinite(q[1]) || Math.abs(q[0]) >= 1e9 || Math.abs(q[1]) >= 1e9) continue;\n\t\tconst last = p[p.length - 1];\n\t\tif (!last || last[0] !== q[0] || last[1] !== q[1]) p.push([q[0], q[1]]);\n\t}\n\tif (p.length < 2) return null;\n\tconst first = p[0];\n\tconst last = p[p.length - 1];\n\tconst [x0, y0, w, h] = bbox(p);\n\tconst size = Math.max(w, h);\n\tconst diag = Math.hypot(w, h);\n\tif (size < minSize) return null;\n\tlet length = 0;\n\tfor (let i = 1; i < p.length; i++) length += dist(p[i - 1], p[i]);\n\tconst gap = dist(first, last);\n\t// A line: the stroke barely leaves the chord between its ends and doesn't double back.\n\tif (gap > 0.85 * length && gap >= minSize) {\n\t\tconst off = Math.max(...p.map((q) => segDistance(first, last, q)));\n\t\tif (off <= Math.max(0.06 * gap, (1.5 * minSize) / 6)) return { kind: 'line', a: first, b: last };\n\t}\n\t// Other shapes are closed: the stroke ends near where it began and goes around.\n\tif (gap > 0.2 * size || length < 2 * size) return null;\n\tconst rms = ellipseRms(p, x0, y0, w, h);\n\tconst roundish = w > 0.2 * size && h > 0.2 * size;\n\tconst ellipse = (): VectorInkShape => {\n\t\tconst [x, y, sw, sh] = snapSquare(x0, y0, w, h);\n\t\treturn { kind: 'ellipse', x, y, width: sw, height: sh };\n\t};\n\tif (roundish && rms < 0.06) return ellipse();\n\tconst c = corners(p, diag);\n\tconst fits = c.length >= 3 && p.every((q) => polygonDistance(c, q) <= 0.08 * diag);\n\tif (fits && c.length === 4 && upright(c)) {\n\t\tconst [bx, by, bw, bh] = bbox(c);\n\t\tconst [x, y, sw, sh] = snapSquare(bx, by, bw, bh);\n\t\treturn { kind: 'rect', x, y, width: sw, height: sh };\n\t}\n\tif (fits && c.length <= VECTOR_INK_MAX_CORNERS) return { kind: 'polygon', points: c };\n\t// A wobbly round stroke is still an ellipse.\n\treturn roundish && rms < 0.1 ? ellipse() : null;\n}\n","/**\n * Pathfinder operands, the Shape Builder and fill regions over a vector document.\n *\n * Everything works in document space: each path is mapped through its own transform and its\n * parents', with live corners cut (`subpathsToBezier`), so rotated nodes and nodes inside\n * rotated groups take part as drawn. The controller maps results back into the space of the node\n * that receives them.\n *\n * - **Pathfinder operands**: a filled path is its fill area (every subpath closed); an unfilled\n * path with a stroke is the area its stroke paints (alignment, caps, joins and arrowheads\n * included) and results from it are filled with the stroke's paint; for Divide an unfilled open\n * path is a cutting line instead.\n * - **Shape Builder**: the planar map of the selected paths (`PlanarMap`). Closed subpaths bound\n * faces whatever their paint (a stroke-only shape's inside is a face), filled open subpaths that\n * enclose an area are closed by an invisible edge, and unfilled open paths cut the faces they\n * cross; their pieces between junctions can be erased. Merging faces unites them along the\n * map's own edges and takes the area away from every shape covering them. Erasing (Alt) also\n * takes the edges of the other outlines, cut where paths meet: a closed subpath opens there,\n * an open one splits, and an edge bounding a face deleted in the same gesture goes with it.\n * - **Fill regions** (paint bucket): the planar map of every visible outline; the region around a\n * point is traced from a ray (`PlanarMap.regionAt`).\n *\n * The Shape Builder follows vectorcraft-main/crates/tools/src/builder.rs (`BuilderMap`, `hit`,\n * `sides`, `line_between`, `erased_edges`, `Hit::Edge`, `shape_builder_shapes`, `sample_polyline`)\n * and crates/engine/src/cmd/buildcmds.rs (`sb_merge`), with figcraft-main/crates/engine/src/shapebuilder.rs\n * (strokes as their outlined area). MIT OR Apache-2.0, © ArtCraft Team and contributors. See\n * THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from '../canvas/types.js';\nimport {\n\tbezierPathBounds,\n\tcontourSignedArea,\n\tcloseContour,\n\tdistanceToBezierPath,\n\tpathToContours,\n\tsegmentPoint,\n\tsegmentTangent,\n\tcontoursToPath,\n\ttype BezierPath,\n\ttype BezierSegment\n} from '../canvas/pathops/bezier.js';\nimport { PlanarMap, cutOutPiece, type PlanarChain } from '../canvas/pathops/planar.js';\nimport type { PathfinderOperation, PathfinderShape } from '../canvas/pathops/pathfinder.js';\nimport { hasPaint, multiplyMatrices, nodeMatrix, subpathsToBezier, vectorPathWorldBezier, vectorStrokeOutline, IDENTITY_MATRIX, type AffineMatrix } from './geometry.js';\nimport { expandVectorAppearance } from './appearance.js';\nimport type { Vec2, VectorDocument, VectorNode, VectorPaint, VectorPathNode, VectorSubpath } from './types.js';\n\n/** A path node with the matrix of its parent space (parent → document). */\nexport interface VectorPathEntry {\n\tnode: VectorPathNode;\n\tparent: AffineMatrix;\n}\n\n/**\n * The path nodes inside `roots` (or every node when `roots` is null), bottom to top, with their\n * parent matrices. Hidden nodes are skipped, and locked ones unless `locked` is true. Live nodes\n * (path effects, live booleans) take part as drawn (`expandVectorAppearance`: their entries are\n * the expanded paths, whose ids may not be in the document) unless `expand` is false.\n */\nexport function vectorPathEntries(doc: VectorDocument, roots: readonly string[] | null, options: { locked?: boolean; expand?: boolean } = {}): VectorPathEntry[] {\n\tconst out: VectorPathEntry[] = [];\n\tconst visit = (stored: VectorNode, parent: AffineMatrix, inside: boolean) => {\n\t\tif (stored.visible === false || (stored.locked && !options.locked)) return;\n\t\tconst node = options.expand === false ? stored : expandVectorAppearance(stored);\n\t\tconst picked = inside || roots === null || roots.includes(node.id);\n\t\tif (node.type === 'group') {\n\t\t\tconst m = multiplyMatrices(nodeMatrix(node), parent);\n\t\t\tfor (const c of node.children) visit(c, m, picked);\n\t\t} else if (picked) out.push({ node, parent });\n\t};\n\tfor (const n of doc.nodes) visit(n, IDENTITY_MATRIX, false);\n\treturn out;\n}\n\nconst usable = (sp: VectorSubpath) => sp.anchors.length > 1;\n\n/** The entry's outline in document space with the given subpaths. */\nfunction worldOf(entry: VectorPathEntry, subpaths: VectorSubpath[]): BezierPath {\n\treturn vectorPathWorldBezier({ ...entry.node, subpaths }, entry.parent);\n}\n\n/** Whether a subpath, closed, encloses an area (rather than retracing itself). */\nexport function subpathEnclosesArea(sp: VectorSubpath): boolean {\n\tconst contour = pathToContours(subpathsToBezier([{ ...sp, closed: true }]))[0];\n\tif (!contour) return false;\n\tconst segs = closeContour(contour, true);\n\tconst b = bezierPathBounds(contoursToPath([{ segments: segs, closed: true }]));\n\tif (!b) return false;\n\tconst w = b.maxX - b.minX;\n\tconst h = b.maxY - b.minY;\n\treturn Math.abs(contourSignedArea(segs)) > 1e-9 * (w * w + h * h);\n}\n\n/** How an operand takes part in a Pathfinder operation: its fill, its stroke's area, a cutting line or its bare outline. */\nexport type VectorPathfinderPaint = 'fill' | 'stroke' | 'line' | 'outline';\n\nconst paintKey = (paint: VectorPaint | undefined) => (typeof paint === 'string' ? paint.trim().toLowerCase() : JSON.stringify(paint ?? null));\n\n/** The Pathfinder shape of a path (document space), and what it stands for. */\nexport function vectorPathfinderShape(entry: VectorPathEntry, op: PathfinderOperation): PathfinderShape & { paint: VectorPathfinderPaint } {\n\tconst { node } = entry;\n\tconst subpaths = node.subpaths.filter(usable);\n\tconst closed = subpaths.map((s) => ({ ...s, closed: true }));\n\tconst fillRule = node.fillRule ?? 'nonzero';\n\tif (hasPaint(node.fill)) return { path: worldOf(entry, closed), fillRule, key: `fill:${paintKey(node.fill)}`, paint: 'fill' };\n\tconst open = subpaths.length > 0 && subpaths.every((s) => !s.closed);\n\tif (op === 'divide' && open) return { path: worldOf(entry, subpaths), line: true, paint: 'line' };\n\tif (hasPaint(node.stroke) && (node.strokeWidth ?? 1) > 0) {\n\t\t// Node transforms are rigid, so the stroke laid out in document space keeps its width.\n\t\tconst path = vectorStrokeOutline({ ...node, subpaths }, entry.parent);\n\t\treturn { path, fillRule: 'nonzero', key: `fill:${paintKey(node.stroke)}`, paint: 'stroke' };\n\t}\n\treturn { path: worldOf(entry, closed), fillRule, key: 'none', paint: 'outline' };\n}\n\nconst STROKE_KEYS = [\n\t'stroke',\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] as const;\n\n/**\n * Style of a Pathfinder or Shape Builder result taken from `node`: the node's own style for a\n * filled area, the stroke's paint as a fill for a stroke's area, or (for Outline edges) a stroke\n * in the paint of the area the edge bounds. Never carries the node's id, transform or geometry.\n */\nexport function vectorResultStyle(node: VectorPathNode, paint: VectorPathfinderPaint, edge = false): Omit<VectorPathNode, 'id' | 'subpaths'> {\n\tconst { id: _id, subpaths: _s, transform: _t, effects: _e, booleanOperation: _b, ...rest } = node;\n\tconst base: Omit<VectorPathNode, 'id' | 'subpaths'> = { ...rest, fillRule: 'nonzero' };\n\tif (edge) {\n\t\tfor (const k of STROKE_KEYS) delete (base as Record<string, unknown>)[k];\n\t\tconst stroke = paint === 'stroke' || !hasPaint(node.fill) ? node.stroke : node.fill;\n\t\treturn { ...base, fill: null, stroke: stroke ?? '#000000', strokeWidth: node.strokeWidth ?? 1 };\n\t}\n\tif (paint === 'stroke') {\n\t\tfor (const k of STROKE_KEYS) delete (base as Record<string, unknown>)[k];\n\t\treturn { ...base, fill: node.stroke ?? null, stroke: null };\n\t}\n\treturn base;\n}\n\n// ---------------------------------------------------------------------------\n// Shape Builder\n// ---------------------------------------------------------------------------\n\n/**\n * A run of a path between the points where other paths meet it (or it ends): a piece of an\n * unfilled open path, or an edge of another path's outline.\n */\nexport interface VectorBuilderLinePiece {\n\tpath: BezierPath;\n\tsegments: BezierSegment[];\n\t/** A loop with no junction on it (the whole subpath). */\n\tclosed: boolean;\n\t/** Index of the entry it belongs to (the front-most where outlines coincide). */\n\tsource: number;\n\tbounds: { minX: number; minY: number; maxX: number; maxY: number };\n}\n\n/** The Shape Builder's planar map of a set of paths. */\nexport interface VectorShapeBuilderMap {\n\tentries: VectorPathEntry[];\n\tmap: PlanarMap;\n\t/** Entries with no closed subpath (cutting lines; their pieces can be erased). */\n\tlines: boolean[];\n\tpieces: VectorBuilderLinePiece[];\n\t/** The edges of the other entries' outlines, cut where paths meet (built on first use: erasing). */\n\treadonly edges: VectorBuilderLinePiece[];\n\t/** How far an edge may lie from the outline it came from (document units). */\n\ttolerance: number;\n}\n\n/** What a Shape Builder gesture touches at a point. */\nexport type VectorBuilderHit = { face: number } | { line: number } | { edge: number };\n\n/** The faces, line pieces and edges a gesture touched, in the order touched. */\nexport interface VectorBuilderTouched {\n\tfaces: number[];\n\tlines: number[];\n\tedges: number[];\n}\n\n/**\n * The Shape Builder map of `entries` (bottom to top). Filled open subpaths that enclose an area\n * are closed. Null when nothing can be mapped.\n */\nexport function vectorShapeBuilderMap(entries: readonly VectorPathEntry[]): VectorShapeBuilderMap | null {\n\tconst lines: boolean[] = [];\n\tconst inputs = entries.map((entry) => {\n\t\tconst filled = hasPaint(entry.node.fill);\n\t\tconst subpaths = entry.node.subpaths.filter(usable).map((sp) => (!sp.closed && filled && subpathEnclosesArea(sp) ? { ...sp, closed: true } : sp));\n\t\tlines.push(!subpaths.some((s) => s.closed));\n\t\treturn { path: worldOf(entry, subpaths), fillRule: entry.node.fillRule ?? 'nonzero' };\n\t});\n\tconst map = PlanarMap.build(inputs);\n\tif (!map) return null;\n\tlet chains: PlanarChain[] | null = null;\n\tconst runs = (line: boolean) =>\n\t\t(chains ??= map.chains())\n\t\t\t.filter((c) => lines[c.source] === line && !(line && c.closed))\n\t\t\t.map((c) => {\n\t\t\t\tconst path = contoursToPath([{ segments: c.segments, closed: c.closed }]);\n\t\t\t\treturn { path, segments: c.segments, closed: c.closed, source: c.source, bounds: bezierPathBounds(path)! };\n\t\t\t});\n\tconst pieces = lines.some(Boolean) ? runs(true) : [];\n\tlet edges: VectorBuilderLinePiece[] | null = null;\n\tlet size = 0;\n\tfor (const input of inputs) {\n\t\tconst b = bezierPathBounds(input.path);\n\t\tif (b) size = Math.max(size, b.maxX - b.minX, b.maxY - b.minY);\n\t}\n\treturn {\n\t\tentries: [...entries],\n\t\tmap,\n\t\tlines,\n\t\tpieces,\n\t\tget edges() {\n\t\t\treturn (edges ??= runs(false));\n\t\t},\n\t\ttolerance: 0.2 * Math.max(size / 2000, 0.25)\n\t};\n}\n\n/** The run of `pieces` nearest `p` within `tol`, with its distance. */\nfunction pieceNear(pieces: readonly VectorBuilderLinePiece[], p: Vec2, tol: number): { index: number; d: number } | null {\n\tconst point: Point = [p.x, p.y];\n\tlet best: { index: number; d: number } | null = null;\n\tpieces.forEach((piece, index) => {\n\t\tconst b = piece.bounds;\n\t\tif (p.x < b.minX - tol || p.x > b.maxX + tol || p.y < b.minY - tol || p.y > b.maxY + tol) return;\n\t\tconst d = distanceToBezierPath(piece.path, point);\n\t\tif (d <= tol && (!best || d < best.d)) best = { index, d };\n\t});\n\treturn best;\n}\n\n/**\n * The face under `p`, or with `lineTolerance` (erasing) first the nearer of the line piece and\n * the outline edge that close.\n */\nexport function vectorShapeBuilderHit(m: VectorShapeBuilderMap, p: Vec2, lineTolerance: number | null = null): VectorBuilderHit | null {\n\tif (lineTolerance !== null && lineTolerance > 0) {\n\t\tconst line = pieceNear(m.pieces, p, lineTolerance);\n\t\tconst edge = pieceNear(m.edges, p, lineTolerance);\n\t\tif (edge && (!line || edge.d < line.d)) return { edge: edge.index };\n\t\tif (line) return { line: line.index };\n\t}\n\tconst face = m.map.faceAt([p.x, p.y]);\n\treturn face >= 0 ? { face } : null;\n}\n\n/** Points along a polyline no more than `step` apart (the pointer's path through faces). */\nexport function samplePolyline(points: readonly Vec2[], step: number): Vec2[] {\n\tconst out: Vec2[] = [];\n\tconst s = Math.max(step, 1e-3);\n\tpoints.forEach((p, i) => {\n\t\tif (i > 0) {\n\t\t\tconst a = points[i - 1];\n\t\t\tconst n = Math.min(4000, Math.ceil(Math.hypot(p.x - a.x, p.y - a.y) / s));\n\t\t\tfor (let k = 1; k < n; k++) out.push({ x: a.x + ((p.x - a.x) * k) / n, y: a.y + ((p.y - a.y) * k) / n });\n\t\t}\n\t\tout.push(p);\n\t});\n\treturn out;\n}\n\n/** Adds what a point hits to `touched` (each face or piece once). */\nexport function addBuilderHit(touched: VectorBuilderTouched, hit: VectorBuilderHit | null): boolean {\n\tif (!hit) return false;\n\tconst [list, i] = 'face' in hit ? [touched.faces, hit.face] : 'line' in hit ? [touched.lines, hit.line] : [touched.edges, hit.edge];\n\tif (list.includes(i)) return false;\n\tlist.push(i);\n\treturn true;\n}\n\n/** The faces just either side of the middle of a line piece (-1 outside every face). */\nfunction sides(m: VectorShapeBuilderMap, piece: VectorBuilderLinePiece): [number, number] {\n\tconst seg = piece.segments[Math.floor(piece.segments.length / 2)];\n\tconst mid = segmentPoint(seg, 0.5);\n\tconst t = segmentTangent(seg, 0.5);\n\tconst chord = Math.hypot(seg[seg.length - 1][0] - seg[0][0], seg[seg.length - 1][1] - seg[0][1]);\n\tconst off = Math.max(1e-6, Math.min(0.05, 0.05 * chord));\n\treturn [m.map.faceAt([mid[0] - t[1] * off, mid[1] + t[0] * off]), m.map.faceAt([mid[0] + t[1] * off, mid[1] - t[0] * off])];\n}\n\n/**\n * The edges a gesture deletes: those it touched that bound none of the faces it touched (an edge\n * of a deleted or merged face goes with the face, so an erase drag that crosses an edge into a\n * face deletes the face).\n */\nexport function vectorBuilderErasedEdges(m: VectorShapeBuilderMap, touched: VectorBuilderTouched): number[] {\n\tif (!touched.edges.length) return [];\n\tconst faces = new Set(touched.faces);\n\treturn touched.edges.filter((k) => m.edges[k] && !sides(m, m.edges[k]).some((f) => f >= 0 && faces.has(f)));\n}\n\n/**\n * What becomes of one entry: unchanged (absent), removed (`null`), or new outlines (document\n * space). `fill`: what is left of its area, filled non-zero (subpaths an erased edge opened stay\n * open); `outline`: its own outline with erased edges cut out, its fill rule kept; `pieces`: the\n * pieces of a cutting line that stay, each its own path.\n */\nexport type VectorBuilderReplacement = { kind: 'fill'; path: BezierPath } | { kind: 'outline'; path: BezierPath } | { kind: 'pieces'; paths: BezierPath[] } | null;\n\nexport interface VectorShapeBuilderPlan {\n\treplace: Map<number, VectorBuilderReplacement>;\n\t/** The merged shape (document space), styled like entry `style`; `currentFill` when only outlines enclosed it. */\n\tmerged: { path: BezierPath; style: number; currentFill: boolean } | null;\n}\n\n/**\n * What a gesture does: merging (or with `erase`, deleting) the touched faces takes them away\n * from every shape covering them; line pieces touched (erasing) or lying between touched faces\n * go; erased edges (`vectorBuilderErasedEdges`) are cut out of every outline they lie on;\n * merging adds the union of the faces as a new shape styled like the top-most shape over the\n * first face touched (or, for a face only outlines enclose, the top-most shape with the current\n * fill).\n */\nexport function planShapeBuilder(m: VectorShapeBuilderMap, touched: VectorBuilderTouched, erase: boolean): VectorShapeBuilderPlan {\n\tconst replace = new Map<number, VectorBuilderReplacement>();\n\tconst picked = new Set(touched.faces);\n\tconst faces = m.map.faces;\n\tconst sourcesOf = faces.map((_, i) => m.map.faceSources(i));\n\tconst cuts = erase ? vectorBuilderErasedEdges(m, touched).map((k) => m.edges[k]) : [];\n\tm.entries.forEach((entry, i) => {\n\t\tif (m.lines[i]) {\n\t\t\tconst own = m.pieces.flatMap((piece, k) => (piece.source === i ? [k] : []));\n\t\t\tconst keep = own.filter((k) => !touched.lines.includes(k) && !sides(m, m.pieces[k]).every((f) => f >= 0 && picked.has(f)));\n\t\t\tif (keep.length === own.length) return;\n\t\t\treplace.set(i, keep.length ? { kind: 'pieces', paths: keep.map((k) => m.pieces[k].path) } : null);\n\t\t\treturn;\n\t\t}\n\t\tconst inUnion = touched.faces.some((f) => sourcesOf[f].includes(i));\n\t\tif (!inUnion && !cuts.length) return;\n\t\tlet rest: BezierPath;\n\t\tif (inUnion) {\n\t\t\tconst kept = faces.flatMap((_, f) => (sourcesOf[f].includes(i) && !picked.has(f) ? [f] : []));\n\t\t\trest = kept.length ? m.map.merge(kept) : [];\n\t\t} else rest = worldOf(entry, entry.node.subpaths.filter(usable));\n\t\t// Each erased edge comes off every outline it lies on (shared edges from each shape).\n\t\tconst fillCloses = hasPaint(entry.node.fill);\n\t\tlet cut = false;\n\t\tfor (const piece of cuts) {\n\t\t\tconst r = rest.length ? cutOutPiece(rest, piece, fillCloses, m.tolerance) : null;\n\t\t\tif (r) {\n\t\t\t\trest = r;\n\t\t\t\tcut = true;\n\t\t\t}\n\t\t}\n\t\tif (!inUnion && !cut) return;\n\t\treplace.set(i, rest.length ? { kind: inUnion ? 'fill' : 'outline', path: rest } : null);\n\t});\n\tlet merged: VectorShapeBuilderPlan['merged'] = null;\n\tif (!erase && touched.faces.length) {\n\t\tconst path = m.map.merge(touched.faces);\n\t\tconst covered = touched.faces.map((f) => sourcesOf[f]).find((s) => s.length);\n\t\tconst lastShape = m.lines.lastIndexOf(false);\n\t\tconst style = covered ? covered[covered.length - 1] : lastShape >= 0 ? lastShape : m.entries.length - 1;\n\t\tif (path.length) merged = { path, style, currentFill: !covered };\n\t}\n\treturn { replace, merged };\n}\n\n// ---------------------------------------------------------------------------\n// Fill regions\n// ---------------------------------------------------------------------------\n\n/**\n * The planar map of every visible path's outline as drawn (locked ones included), for the paint\n * bucket. `gap` closes gaps up to that distance between loose ends.\n */\nexport function vectorFillMap(doc: VectorDocument, gap = 0): PlanarMap | null {\n\tconst entries = vectorPathEntries(doc, null, { locked: true });\n\treturn PlanarMap.build(\n\t\tentries.map((e) => ({ path: worldOf(e, e.node.subpaths.filter(usable)), fills: false })),\n\t\t{ gap, maxSegments: 20000 }\n\t);\n}\n","import { HistoryManager } from '../history/HistoryManager.js';\nimport type { Command } from '../history/types.js';\nimport { fitCubics } from '../canvas/curveFit.js';\nimport { pathBoolean } from '../canvas/pathops/boolean.js';\nimport { pathfinder as runPathfinder } from '../canvas/pathops/pathfinder.js';\nimport type { PlanarMap } from '../canvas/pathops/planar.js';\nimport { bezierPathBounds, bezierPathToSvg, transformBezierPath, type BezierPath } from '../canvas/pathops/bezier.js';\nimport type { Point } from '../canvas/types.js';\nimport {\n\tbezierToSubpaths,\n\tboundsFromPoints,\n\tboundsIntersect,\n\tcloneVectorDocument,\n\tcloneVectorNode,\n\tcollectPathNodes,\n\tconvertAnchor,\n\tellipseSubpath,\n\tfindVectorNode,\n\thitTestVectorNode,\n\thitTestVectorNodes,\n\tinsertAnchor,\n\tlineSubpath,\n\tlocateVectorNode,\n\tmoveAnchorHandle,\n\tnearestSegment,\n\thasPaint,\n\tnodeMatrix,\n\tinvertMatrix,\n\tapplyMatrix,\n\tapplyMatrixLinear,\n\tmultiplyMatrices,\n\tmatrixRotation,\n\tvectorWorldMatrix,\n\tvectorParentMatrix,\n\tvectorPathWorldBezier,\n\tIDENTITY_MATRIX,\n\tpolygonSubpath,\n\trectSubpath,\n\tremoveAnchors,\n\treverseSubpath,\n\trotateMatrix,\n\tscaleMatrix,\n\tstarSubpath,\n\tsubpathsToBezier,\n\ttransformVectorNode,\n\ttranslateAnchor,\n\ttranslateMatrix,\n\tunionBounds,\n\tvectorNodeBounds,\n\twalkVectorNodes,\n\ttype AffineMatrix\n} from './geometry.js';\nimport {\n\tcornerBisectorPoint,\n\tcornerRadiusChange,\n\tfitCornerRadius,\n\tnextVectorCornerKind,\n\tsetAnchorCorner,\n\tsetSubpathCorners,\n\tvectorCorners,\n\ttype VectorCornerInfo\n} from './corners.js';\nimport { strokeWidthProfileOf, vectorStrokeOutlinePath, vectorStrokeParts } from './stroke.js';\nimport { nearestStrokePosition, strokePositionPoint } from './varstroke.js';\nimport {\n\tgenerateWidthProfile,\n\tinsertWidthPoint,\n\tpressureWidthProfile,\n\tremoveWidthPoints,\n\treverseWidthProfile,\n\tsanitizeWidthProfile,\n\tsetWidthPoint,\n\twidthProfileAt,\n\twidthProfileMaxScale,\n\twidthProfilePreset,\n\ttype VectorTaperOptions,\n\ttype VectorWaveOptions,\n\ttype VectorWidthPreset,\n\tVECTOR_WIDTH_PROFILE_MAX_POINTS,\n\tVECTOR_WIDTH_SAME_T\n} from './width.js';\nimport { parseVectorSvg, vectorDocumentToSvg, type VectorSvgExportOptions } from './svg.js';\nimport {\n\texpandVectorBlend,\n\tisVectorBlendGroup,\n\trefreshVectorBlends,\n\treleaseVectorBlend,\n\tvectorBlendKeyTargets\n} from './blendGroup.js';\nimport {\n\tcreateVectorEffect,\n\texpandVectorAppearance,\n\thasOwnVectorAppearance,\n\thasVectorAppearance,\n\tisVectorLiveBoolean,\n\tnormalizeVectorEffectParams,\n\tvectorAppearanceOutline,\n\tvectorEnteredGroups,\n\tVECTOR_EFFECT_LABELS\n} from './appearance.js';\nimport { constrainToLeave, VECTOR_CONSTRUCTION_ANGLES, VectorSnapTargets, type VectorSnapGuides, type VectorSnapLeave } from './snap.js';\nimport {\n\tbendSegment as bendSubpathSegment,\n\tcurvatureMoveAnchor,\n\tcutSubpathAt,\n\tjoinSubpaths,\n\tremoveAnchorsKeepingShape,\n\tremoveRedundantPoints as removeRedundantSubpathPoints,\n\tsmoothSubpath,\n\tsplitSubpathAt\n} from './pathEdit.js';\nimport { eraseFromFill, eraseFromOutline, eraserRegion, knifePath, polylineBounds, simplifyPolyline } from './pathCut.js';\nimport { LazyMouse, recognizeInkShape, vectorInkShapeName, type VectorInkShape } from './ink.js';\nimport {\n\taddBuilderHit,\n\tplanShapeBuilder,\n\tsamplePolyline,\n\tvectorFillMap,\n\tvectorPathEntries,\n\tvectorPathfinderShape,\n\tvectorResultStyle,\n\tvectorBuilderErasedEdges,\n\tvectorShapeBuilderHit,\n\tvectorShapeBuilderMap,\n\ttype VectorBuilderTouched,\n\ttype VectorShapeBuilderMap\n} from './builder.js';\nimport type {\n\tVec2,\n\tVectorEffect,\n\tVectorEffectType,\n\tVectorAlign,\n\tVectorAnchor,\n\tVectorAnchorRef,\n\tVectorAnchorType,\n\tVectorArrange,\n\tVectorAverageAxis,\n\tVectorBlend,\n\tVectorBlendOptions,\n\tVectorBooleanOperation,\n\tVectorBounds,\n\tVectorCornerKind,\n\tVectorDefaults,\n\tVectorDistribute,\n\tVectorDocument,\n\tVectorGroupNode,\n\tVectorHandleRef,\n\tVectorModifiers,\n\tVectorNode,\n\tVectorPathfinderOperation,\n\tVectorPathNode,\n\tVectorSegmentRef,\n\tVectorStyle,\n\tVectorSubpath,\n\tVectorTool,\n\tVectorTransformHandle,\n\tVectorViewport,\n\tVectorWidthPoint\n} from './types.js';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface VectorSnapOptions {\n\t/** Grid size in document units (0 = off, default 0). Points and boxes that snap to no object snap to it. */\n\tgrid: number;\n\t/**\n\t * Snap to other objects (default true): anchors, paths and their intersections, bounding-box\n\t * edges and centres, the artboard, construction angles, equal spacing and matching sizes.\n\t */\n\tobjects: boolean;\n\t/** Show smart guide lines and labels while snapping (default true). */\n\tguides: boolean;\n\t/** How near a target pulls, in screen pixels (default 5). */\n\tthreshold: number;\n\t/** Construction guide angles through the point a drawn point leaves from, in degrees (default 0, 45, 90, 135). */\n\tangles: readonly number[];\n}\n\n/** Snap options new editors start with. Hold Cmd / Ctrl (`mod`) during a drag to bypass snapping. */\nexport const DEFAULT_VECTOR_SNAP: Readonly<VectorSnapOptions> = { grid: 0, objects: true, guides: true, threshold: 5, angles: VECTOR_CONSTRUCTION_ANGLES };\n\n/** How path-editing gestures and the pencil, knife and eraser behave. */\nexport interface VectorPathEditOptions {\n\t/**\n\t * Deleting anchors refits the neighbouring handles so the shape stays (default true). Off,\n\t * the neighbours are joined with their handles as they are.\n\t */\n\tkeepShapeOnDelete: boolean;\n\t/**\n\t * Dragging one anchor with the direct tool bends its two segments like the Curvature tool,\n\t * keeping the segments beyond still (default false). Alt held during the drag flips it.\n\t */\n\tcurvatureMoves: boolean;\n\t/** Pencil lazy-mouse smoothing, 0–1: the pulled string is up to 100 screen pixels long (default 0, off). */\n\tpencilSmoothing: number;\n\t/** Pencil strokes that look like a line, rectangle, ellipse or polygon become that shape (default false). */\n\trecognizeShapes: boolean;\n\t/** Eraser diameter in document units (default 10). */\n\teraserSize: number;\n\t/**\n\t * Record pointer pressure on pencil strokes as a width profile: `pen` (default) for pen input\n\t * only, `all` for any pointer that reports pressure, `off` never.\n\t */\n\tpencilPressure: 'pen' | 'all' | 'off';\n\t/**\n\t * Paint bucket: gaps up to this distance (document units) between loose ends of outlines are\n\t * closed when finding the region to fill (default 0, off).\n\t */\n\tfillGap: number;\n}\n\n/** The gap the toolbars' \"Close gaps\" toggle sets (document units). */\nexport const VECTOR_FILL_GAP = 6;\n\n/** Path-editing options new editors start with. */\nexport const DEFAULT_VECTOR_PATH_EDIT: Readonly<VectorPathEditOptions> = {\n\tkeepShapeOnDelete: true,\n\tcurvatureMoves: false,\n\tpencilSmoothing: 0,\n\trecognizeShapes: false,\n\teraserSize: 10,\n\tpencilPressure: 'pen',\n\tfillGap: 0\n};\n\n/** Longest pencil lazy-mouse string (at smoothing 1), in screen pixels. */\nexport const VECTOR_PENCIL_STRING_PX = 100;\n\nexport interface VectorEditorOptions {\n\tdocument?: VectorDocument;\n\t/** History to record into, e.g. one shared with the rest of the app (default: a new one). */\n\thistory?: HistoryManager;\n\t/** Starting tool (default `select`). */\n\ttool?: VectorTool;\n\t/** Style and settings for new shapes and paths. */\n\tdefaults?: VectorDefaults;\n\tviewport?: Partial<VectorViewport>;\n\tsnap?: Partial<VectorSnapOptions>;\n\tediting?: Partial<VectorPathEditOptions>;\n\t/** Pick distance for anchors, handles and outlines in screen pixels (default 6). */\n\thitRadius?: number;\n\t/** Id factory for new nodes. */\n\tcreateId?: () => string;\n\t/** Called after the document changes (edits, undo, redo, load). */\n\tonChange?: (document: VectorDocument) => void;\n}\n\n/** A pointer event as the controller needs it. Positions are in screen pixels relative to the canvas. */\nexport interface VectorPointerInput extends VectorModifiers {\n\t/** 0 = primary, 1 = middle (pans), 2 = secondary. Default 0. */\n\tbutton?: number;\n\t/** `PointerEvent.pressure` (0–1), recorded on pencil strokes. */\n\tpressure?: number;\n\t/** `PointerEvent.pointerType` (`mouse`, `pen`, `touch`). */\n\tpointerType?: string;\n}\n\n/** One width point of a path's stroke profile; `subpath` is the subpath it is shown on. */\nexport interface VectorWidthPointRef {\n\tnodeId: string;\n\tindex: number;\n\tsubpath: number;\n}\n\n/** A spot on a stroke (width tool): where a drag would add a width point. Positions in document units. */\nexport interface VectorStrokeSpot {\n\tnodeId: string;\n\tsubpath: number;\n\t/** Position along the subpath by length, 0–1. */\n\tt: number;\n\tpoint: Vec2;\n\t/** Ends of the stroke's width across the path there. */\n\tleft: Vec2;\n\tright: Vec2;\n}\n\n/**\n * The width point being edited numerically (double-click with the width tool): `index` is the\n * point's, or null for a new point at `t` (a double-click on a stroke away from its points).\n * `left` / `right` are side widths in document units (how far the stroke reaches on each side),\n * `point` is where it sits on the path, in document space.\n */\nexport interface VectorWidthPointEdit {\n\tnodeId: string;\n\tsubpath: number;\n\tindex: number | null;\n\tt: number;\n\tleft: number;\n\tright: number;\n\tpoint: Vec2;\n}\n\n/** A key press as the controller needs it. */\nexport interface VectorKeyInput extends VectorModifiers {\n\t/** `KeyboardEvent.key`. */\n\tkey: string;\n}\n\n/** What the pointer is over. */\nexport interface VectorHover {\n\tnodeId: string | null;\n\tanchor?: VectorAnchorRef;\n\thandle?: VectorHandleRef;\n\t/** A segment under the pointer (direct tool and scissors); `point` is in document space. */\n\tsegment?: { nodeId: string; subpath: number; segment: number; t: number; point: Vec2 };\n\ttransformHandle?: VectorTransformHandle;\n\t/** A live corner widget (direct tool): the corner's anchor. */\n\tcorner?: VectorAnchorRef;\n\t/** A width point (width tool): its centre or the end of its left / right side. */\n\twidthPoint?: VectorWidthPointRef & { part: 'center' | 'left' | 'right' };\n\t/** A spot on a stroke away from its width points (width tool). */\n\tstrokeSpot?: VectorStrokeSpot;\n}\n\n/** The drag in progress, for overlays and status text. */\nexport type VectorInteraction =\n\t| { type: 'marquee'; bounds: VectorBounds }\n\t| { type: 'move'; dx: number; dy: number }\n\t| { type: 'scale'; handle: VectorTransformHandle; sx: number; sy: number }\n\t| { type: 'rotate'; angle: number }\n\t| { type: 'anchors'; dx: number; dy: number }\n\t| { type: 'handle'; ref: VectorHandleRef }\n\t| { type: 'bend'; ref: VectorSegmentRef }\n\t| { type: 'corner'; ref: VectorAnchorRef; radius: number }\n\t| { type: 'draw'; tool: VectorTool; nodeId: string; bounds: VectorBounds; string?: [Vec2, Vec2] }\n\t| { type: 'stroke'; tool: 'knife' | 'eraser'; points: Vec2[]; width: number }\n\t| { type: 'builder'; mode: 'merge' | 'erase'; points: Vec2[] }\n\t| { type: 'width'; ref: VectorWidthPointRef; mode: 'width' | 'move' | 'copy'; width: number }\n\t| { type: 'pen'; nodeId: string }\n\t| { type: 'pan' };\n\n/** A snapshot of the editor. The same object is returned until something changes. */\nexport interface VectorEditorState {\n\tdocument: VectorDocument;\n\ttool: VectorTool;\n\t/** Selected node ids (top-level nodes or group members picked with the direct tool). */\n\tselection: readonly string[];\n\t/** Selected anchors (direct tool). */\n\tselectedAnchors: readonly VectorAnchorRef[];\n\t/** The selected width points (width tool, all on one path); Delete removes them. */\n\tselectedWidthPoints: readonly VectorWidthPointRef[];\n\t/** The width point open for numeric editing (double-click with the width tool), if any. */\n\twidthPointEdit: VectorWidthPointEdit | null;\n\t/** The path whose anchors are being edited, if any. */\n\teditingId: string | null;\n\t/** Axis-aligned bounds of the selected nodes in document units. */\n\tselectionBounds: VectorBounds | null;\n\t/** The selection box, rotated with the selection when its nodes share a rotation. */\n\tselectionFrame: VectorSelectionFrame | null;\n\tviewport: VectorViewport;\n\tdefaults: Readonly<Required<Pick<VectorDefaults, 'polygonSides' | 'starPoints' | 'starInnerRatio' | 'cornerRadius'>> & Pick<VectorDefaults, 'cornerKind'> & VectorStyle>;\n\tsnap: VectorSnapOptions;\n\tediting: VectorPathEditOptions;\n\t/** Smart guides of the snap in progress, in document units (null when there are none). */\n\tguides: VectorSnapGuides | null;\n\thitRadius: number;\n\thover: VectorHover | null;\n\t/** A CSS cursor for the canvas. */\n\tcursor: string;\n\tinteraction: VectorInteraction | null;\n\t/** The path the pen tool is adding to. */\n\tpen: { nodeId: string; subpath: number } | null;\n\t/** Last pointer position in document units (for the pen preview), or null when outside. */\n\tpointer: Vec2 | null;\n\t/** Shape Builder and paint bucket highlight: faces, line pieces and the gesture's trail. */\n\tbuilder: VectorBuilderPreview | null;\n\t/** The Blend tool's first click: the shape (and anchor of its first subpath) the next click blends from. */\n\tblendStart: VectorBlendStart | null;\n\t/** Options new blends get. */\n\tblendDefaults: VectorBlendOptions;\n\tcanUndo: boolean;\n\tcanRedo: boolean;\n\tundoLabel: string;\n\tredoLabel: string;\n\t/** Bumped on every change. */\n\trevision: number;\n}\n\nexport type VectorActionResult = { ok: true } | { ok: false; message: string };\n\n/** The shape the Blend tool blends from (its first click). */\nexport interface VectorBlendStart {\n\tnodeId: string;\n\t/** The clicked anchor of its first subpath, or null for the shape as a whole. */\n\tanchor: number | null;\n}\n\n/** Blend settings and options of `VectorEditorController.blend`. */\nexport interface VectorBlendInput extends VectorBlendOptions {\n\t/** Nodes to blend (default: the selection), in stacking order unless `keepOrder`. */\n\tids?: readonly string[];\n\t/** Blend in the order given instead of stacking order. */\n\tkeepOrder?: boolean;\n\t/** Start anchor of each key's first subpath, by key id. */\n\tstarts?: Record<string, number>;\n\tname?: string;\n}\n\nconst BLEND_OPTION_KEYS = ['spacing', 'steps', 'distance', 'orientToSpine', 'linear'] as const;\n\n/** The blend options in `input`, valid ones only. */\nfunction blendOptionsOf(input: Partial<VectorBlendOptions>): VectorBlendOptions {\n\tconst out: VectorBlendOptions = {};\n\tif (input.spacing === 'steps' || input.spacing === 'distance' || input.spacing === 'smoothColor') out.spacing = input.spacing;\n\tif (typeof input.steps === 'number' && Number.isFinite(input.steps)) out.steps = Math.min(1000, Math.max(1, Math.round(input.steps)));\n\tif (typeof input.distance === 'number' && Number.isFinite(input.distance) && input.distance > 0) out.distance = input.distance;\n\tif (typeof input.orientToSpine === 'boolean') out.orientToSpine = input.orientToSpine;\n\tif (typeof input.linear === 'boolean') out.linear = input.linear;\n\treturn out;\n}\n\n/**\n * What the Shape Builder or paint bucket highlights, in document units: the faces (filled paths\n * as SVG path data) under the pointer or touched by the drag, the line pieces an erase would\n * delete, and the drag's trail. `mode` is `merge`, `erase` (Alt) or `fill` (paint bucket).\n */\nexport interface VectorBuilderPreview {\n\tmode: 'merge' | 'erase' | 'fill';\n\tfaces: BezierPath[];\n\tlines: BezierPath[];\n\ttrail: Vec2[] | null;\n}\n\n/** Result of a Shape Builder gesture. */\nexport interface VectorShapeBuilderResult {\n\tok: boolean;\n\tmessage?: string;\n\t/** Id of the merged shape (none when erasing). */\n\tmerged: string | null;\n\tfaces: number;\n\tlines: number;\n\t/** Outline edges erased (cut out of the paths they lie on). */\n\tedges: number;\n}\n\n/** A face of the Shape Builder map (document units). */\nexport interface VectorShapeBuilderFace {\n\tindex: number;\n\t/** SVG path data. */\n\td: string;\n\tarea: number;\n\t/** Ids of the paths whose fill covers it, bottom to top (empty for an area only outlines enclose). */\n\tsources: string[];\n\t/** A point inside it. */\n\tpoint: Vec2 | null;\n}\n\nexport interface AddVectorNodeOptions {\n\t/** Group to add into (default: the root). */\n\tparentId?: string;\n\t/** Position in the parent's list (default: on top). */\n\tindex?: number;\n\t/** Select the new node (default true). */\n\tselect?: boolean;\n\t/** Undo step label. */\n\tlabel?: string;\n}\n\n/** A shape description for `addShape` (and the agent command of the same name). */\nexport interface VectorShapeInput extends VectorStyle {\n\tkind: 'rect' | 'ellipse' | 'polygon' | 'star' | 'line';\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\t/** Live corner radius of a rectangle, polygon or star (default: the editor default). */\n\tcornerRadius?: number;\n\tcornerKind?: VectorCornerKind;\n\tsides?: number;\n\tpoints?: number;\n\tinnerRatio?: number;\n\tname?: string;\n}\n\n/** Every field of `VectorStyle`, in the order style panels and commands list them. */\nexport const VECTOR_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 satisfies readonly (keyof VectorStyle)[];\n\n/** Clipboard payload type written by `copy()`. */\nexport const VECTOR_CLIPBOARD_TYPE = 'pixerate/vector';\n\nconst DEFAULT_DEFAULTS = {\n\tfill: '#d4d4d8',\n\tstroke: '#18181b',\n\tstrokeWidth: 2,\n\tpolygonSides: 6,\n\tstarPoints: 5,\n\tstarInnerRatio: 0.5,\n\tcornerRadius: 0\n};\n\nconst MIN_ZOOM = 0.05;\nconst MAX_ZOOM = 64;\n/** Screen distance of the rotation handle above the selection box. */\nexport const VECTOR_ROTATE_HANDLE_OFFSET = 24;\n\n// ---------------------------------------------------------------------------\n// Helpers shared with the render module\n// ---------------------------------------------------------------------------\n\nconst sameRef = (a: VectorAnchorRef, b: VectorAnchorRef) => a.nodeId === b.nodeId && a.subpath === b.subpath && a.index === b.index;\n\n/** Ids of the path nodes whose anchors are shown (the edited path plus selected paths). */\nexport function getEditablePathIds(state: Pick<VectorEditorState, 'document' | 'selection' | 'editingId' | 'tool' | 'pen'>): string[] {\n\tif (state.tool !== 'direct' && state.tool !== 'pen') return [];\n\tconst ids = new Set<string>();\n\tif (state.pen) ids.add(state.pen.nodeId);\n\tif (state.tool === 'direct') {\n\t\tif (state.editingId) ids.add(state.editingId);\n\t\tfor (const id of state.selection) {\n\t\t\tconst node = findVectorNode(state.document.nodes, id);\n\t\t\tif (node && !node.locked) for (const p of collectPathNodes(node)) if (!p.locked) ids.add(p.id);\n\t\t}\n\t}\n\treturn [...ids].filter((id) => findVectorNode(state.document.nodes, id)?.type === 'path');\n}\n\n/** Every anchor drawn on the canvas. */\nexport function getVisibleAnchors(state: Pick<VectorEditorState, 'document' | 'selection' | 'editingId' | 'tool' | 'pen'>): VectorAnchorRef[] {\n\tconst out: VectorAnchorRef[] = [];\n\tfor (const id of getEditablePathIds(state)) {\n\t\tconst node = findVectorNode(state.document.nodes, id) as VectorPathNode;\n\t\tnode.subpaths.forEach((sp, s) => sp.anchors.forEach((_, i) => out.push({ nodeId: id, subpath: s, index: i })));\n\t}\n\treturn out;\n}\n\n/**\n * Handles drawn on the canvas: those of selected anchors and of the segments next to them\n * (direct tool), or of the last anchor of the pen path.\n */\nexport function getVisibleHandles(\n\tstate: Pick<VectorEditorState, 'document' | 'selection' | 'editingId' | 'tool' | 'pen' | 'selectedAnchors'>\n): VectorHandleRef[] {\n\tconst out: VectorHandleRef[] = [];\n\tconst seen = new Set<string>();\n\tconst push = (node: VectorPathNode, s: number, i: number, handle: 'in' | 'out') => {\n\t\tconst sp = node.subpaths[s];\n\t\tconst a = sp?.anchors[i];\n\t\tif (!a || !a[handle]) return;\n\t\tconst key = `${node.id}:${s}:${i}:${handle}`;\n\t\tif (seen.has(key)) return;\n\t\tseen.add(key);\n\t\tout.push({ nodeId: node.id, subpath: s, index: i, handle });\n\t};\n\tif (state.tool === 'pen' && state.pen) {\n\t\tconst node = findVectorNode(state.document.nodes, state.pen.nodeId);\n\t\tconst sp = node?.type === 'path' ? node.subpaths[state.pen.subpath] : undefined;\n\t\tif (node?.type === 'path' && sp) {\n\t\t\tconst last = sp.anchors.length - 1;\n\t\t\tpush(node, state.pen.subpath, last, 'in');\n\t\t\tpush(node, state.pen.subpath, last, 'out');\n\t\t}\n\t\treturn out;\n\t}\n\tif (state.tool !== 'direct') return out;\n\tconst editable = new Set(getEditablePathIds(state));\n\tfor (const ref of state.selectedAnchors) {\n\t\tif (!editable.has(ref.nodeId)) continue;\n\t\tconst node = findVectorNode(state.document.nodes, ref.nodeId) as VectorPathNode;\n\t\tconst sp = node.subpaths[ref.subpath];\n\t\tif (!sp) continue;\n\t\tconst n = sp.anchors.length;\n\t\tpush(node, ref.subpath, ref.index, 'in');\n\t\tpush(node, ref.subpath, ref.index, 'out');\n\t\tconst prev = ref.index > 0 ? ref.index - 1 : sp.closed ? n - 1 : -1;\n\t\tconst next = ref.index < n - 1 ? ref.index + 1 : sp.closed ? 0 : -1;\n\t\tif (prev >= 0) push(node, ref.subpath, prev, 'out');\n\t\tif (next >= 0) push(node, ref.subpath, next, 'in');\n\t}\n\treturn out;\n}\n\n/** Live corner widgets sit at least this far inside their corner (screen px along each side of a right angle). */\nexport const VECTOR_CORNER_WIDGET_INSET = 10;\n\n/** A live corner widget of the direct tool: drag it to round the corners, Alt-click to change their kind. */\nexport interface VectorCornerWidget {\n\tref: VectorAnchorRef;\n\t/** Document position of the widget. */\n\tx: number;\n\ty: number;\n\t/** The radius stored on the anchor and the radius drawn (no larger than fits). */\n\tradius: number;\n\tdrawnRadius: number;\n\tkind: VectorCornerKind;\n\t/** The corner in its node's own space. */\n\tcorner: VectorCornerInfo;\n}\n\n/**\n * The live corner widgets the direct tool shows: on each editable path, at its selected corner\n * anchors (or at every corner when none of its anchors is selected). Corners whose sides are\n * shorter than three insets on screen hide theirs. Dragging one sets the radius of all of them.\n * Ported from ArtCraft vectorcraft `crates/tools/src/corners.rs` (MIT OR Apache-2.0, see\n * THIRD_PARTY_NOTICES.md), as is the `corner` drag below.\n */\nexport function getVectorCornerWidgets(\n\tstate: Pick<VectorEditorState, 'document' | 'selection' | 'editingId' | 'tool' | 'pen' | 'selectedAnchors' | 'viewport'>\n): VectorCornerWidget[] {\n\tif (state.tool !== 'direct' || state.pen) return [];\n\tconst zoom = state.viewport.zoom;\n\tconst out: VectorCornerWidget[] = [];\n\tfor (const id of getEditablePathIds(state)) {\n\t\tconst node = findVectorNode(state.document.nodes, id) as VectorPathNode;\n\t\tconst picked = state.selectedAnchors.filter((r) => r.nodeId === id);\n\t\tconst m = vectorWorldMatrix(state.document.nodes, id) ?? IDENTITY_MATRIX;\n\t\tfor (const c of vectorCorners(node.subpaths)) {\n\t\t\tif (picked.length && !picked.some((r) => r.subpath === c.subpath && r.index === c.index)) continue;\n\t\t\tconst side = Math.min(c.lu, c.lv);\n\t\t\tif (side * zoom < 3 * VECTOR_CORNER_WIDGET_INSET) continue;\n\t\t\tconst a = node.subpaths[c.subpath].anchors[c.index];\n\t\t\tconst radius = a.corner?.radius ?? 0;\n\t\t\tconst drawn = fitCornerRadius(c, radius);\n\t\t\t// On the bisector at the centre of the arc as drawn, at least the inset in and at most half the shorter side.\n\t\t\tconst k = Math.min(Math.max(drawn / c.sin, VECTOR_CORNER_WIDGET_INSET / (zoom * Math.sqrt(1 + c.cos))), side / 2);\n\t\t\tconst p = applyMatrix(m, cornerBisectorPoint(c, k));\n\t\t\tout.push({ ref: { nodeId: id, subpath: c.subpath, index: c.index }, x: p.x, y: p.y, radius, drawnRadius: drawn, kind: a.corner?.kind ?? 'round', corner: c });\n\t\t}\n\t}\n\treturn out;\n}\n\n/**\n * The line a path's stroke follows (node space; shortened under `tip` arrowheads) and the\n * distance a side scale of 1 reaches from it (half the drawn width: twice the stroke width for\n * inside / outside strokes). Null for paths without a visible stroke.\n */\nexport function vectorStrokeWidthGeometry(node: VectorPathNode): { line: BezierPath; unit: number } | null {\n\tif (!hasPaint(node.stroke) || !((node.strokeWidth ?? 1) > 0)) return null;\n\tconst parts = vectorStrokeParts(subpathsToBezier(node.subpaths), node);\n\treturn parts.line.length ? { line: parts.line, unit: parts.drawWidth / 2 } : null;\n}\n\n/** A width point of the width tool, in document units. */\nexport interface VectorWidthHandle {\n\tref: VectorWidthPointRef;\n\t/** The point on the path. */\n\tx: number;\n\ty: number;\n\t/** Ends of its left and right sides. */\n\tleft: Vec2;\n\tright: Vec2;\n\tt: number;\n\t/** Side scales (multiples of half the stroke width). */\n\tleftScale: number;\n\trightScale: number;\n\tselected: boolean;\n}\n\n/**\n * Moves the points at `indices` of `profile` by `dt` along the path (kept in 0–1) and re-sorts\n * it (stably, so steps keep their order); `renumber` maps each old index to its new one.\n */\nfunction shiftWidthPoints(profile: readonly VectorWidthPoint[], indices: readonly number[], dt: number): { points: VectorWidthPoint[]; renumber: Map<number, number> } {\n\tconst moving = new Set(indices);\n\tconst tagged = profile.map((p, i) => ({ p: moving.has(i) ? { ...p, t: Math.min(Math.max(p.t + dt, 0), 1) } : { ...p }, i }));\n\ttagged.sort((a, b) => a.p.t - b.p.t || a.i - b.i);\n\treturn { points: tagged.map((x) => x.p), renumber: new Map(tagged.map((x, k) => [x.i, k])) };\n}\n\n/**\n * The width points the width tool shows: every profile point on every subpath of the stroke\n * under the pointer, the selected paths and the stroke being edited. Ported from ArtCraft\n * vectorcraft `crates/tools/src/distort/width.rs` (MIT OR Apache-2.0, see\n * THIRD_PARTY_NOTICES.md), as are the `width` drag and hover below.\n */\nexport function getVectorWidthHandles(\n\tstate: Pick<VectorEditorState, 'document' | 'selection' | 'tool' | 'hover' | 'interaction' | 'selectedWidthPoints'>\n): VectorWidthHandle[] {\n\tif (state.tool !== 'width') return [];\n\tconst ids = new Set<string>();\n\tfor (const id of state.selection) {\n\t\tconst n = findVectorNode(state.document.nodes, id);\n\t\tif (n) for (const p of collectPathNodes(n)) ids.add(p.id);\n\t}\n\tif (state.hover?.nodeId) ids.add(state.hover.nodeId);\n\tif (state.interaction?.type === 'width') ids.add(state.interaction.ref.nodeId);\n\tfor (const r of state.selectedWidthPoints) ids.add(r.nodeId);\n\tconst out: VectorWidthHandle[] = [];\n\tfor (const id of ids) {\n\t\tconst node = findVectorNode(state.document.nodes, id);\n\t\tif (node?.type !== 'path' || node.visible === false || !node.strokeWidthProfile?.length) continue;\n\t\tconst g = vectorStrokeWidthGeometry(node);\n\t\tif (!g) continue;\n\t\tconst profile = sanitizeWidthProfile(node.strokeWidthProfile);\n\t\tconst m = vectorWorldMatrix(state.document.nodes, id) ?? IDENTITY_MATRIX;\n\t\tconst contours = node.subpaths.length;\n\t\tfor (let sp = 0; sp < contours; sp++) {\n\t\t\tprofile.forEach((w, index) => {\n\t\t\t\tconst at = strokePositionPoint(g.line, sp, w.t);\n\t\t\t\tif (!at) return;\n\t\t\t\tconst n = { x: at.tangent[1], y: -at.tangent[0] };\n\t\t\t\tconst c = { x: at.point[0], y: at.point[1] };\n\t\t\t\tconst p = applyMatrix(m, c);\n\t\t\t\tconst l = applyMatrix(m, { x: c.x + n.x * w.left * g.unit, y: c.y + n.y * w.left * g.unit });\n\t\t\t\tconst r = applyMatrix(m, { x: c.x - n.x * w.right * g.unit, y: c.y - n.y * w.right * g.unit });\n\t\t\t\tout.push({\n\t\t\t\t\tref: { nodeId: id, index, subpath: sp },\n\t\t\t\t\tx: p.x,\n\t\t\t\t\ty: p.y,\n\t\t\t\t\tleft: l,\n\t\t\t\t\tright: r,\n\t\t\t\t\tt: w.t,\n\t\t\t\t\tleftScale: w.left,\n\t\t\t\t\trightScale: w.right,\n\t\t\t\t\tselected: state.selectedWidthPoints.some((r) => r.nodeId === id && r.index === index)\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Looks up an anchor (positions in its node's own space). */\nexport function getAnchor(doc: VectorDocument, ref: VectorAnchorRef): VectorAnchor | undefined {\n\tconst node = findVectorNode(doc.nodes, ref.nodeId);\n\treturn node?.type === 'path' ? node.subpaths[ref.subpath]?.anchors[ref.index] : undefined;\n}\n\n/** An anchor with its position and handles in document space (its node's transforms applied). */\nexport function getAnchorWorld(doc: VectorDocument, ref: VectorAnchorRef): { x: number; y: number; in?: Vec2; out?: Vec2; type?: VectorAnchorType } | undefined {\n\tconst a = getAnchor(doc, ref);\n\tif (!a) return undefined;\n\tconst m = vectorWorldMatrix(doc.nodes, ref.nodeId) ?? IDENTITY_MATRIX;\n\tconst p = applyMatrix(m, a);\n\treturn { x: p.x, y: p.y, ...(a.in ? { in: applyMatrix(m, a.in) } : {}), ...(a.out ? { out: applyMatrix(m, a.out) } : {}), ...(a.type ? { type: a.type } : {}) };\n}\n\n/**\n * The selection box: `angle` is the shared rotation of the selected nodes (0 when they\n * differ), `x / y / width / height` the bounds in the box's own frame (document space\n * rotated by −angle). A frame point `q` is at `rotateMatrix(angle)` applied to `q`.\n */\nexport interface VectorSelectionFrame {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\tangle: number;\n}\n\n/** The selection frame of nodes `ids` (see `VectorSelectionFrame`), or null when they have no points. */\nexport function getVectorSelectionFrame(doc: VectorDocument, ids: readonly string[]): VectorSelectionFrame | null {\n\tconst nodes = ids.map((id) => ({ node: findVectorNode(doc.nodes, id), parent: vectorParentMatrix(doc.nodes, id) ?? IDENTITY_MATRIX })).filter((n) => n.node);\n\tif (!nodes.length) return null;\n\tconst angles = nodes.map((n) => matrixRotation(multiplyMatrices(nodeMatrix(n.node!), n.parent)));\n\tconst same = angles.every((a) => Math.abs(normalizeAngle(a - angles[0])) < 1e-6);\n\tconst angle = same ? angles[0] : 0;\n\tconst unrotate = rotateMatrix(-angle);\n\tconst b = unionBounds(nodes.map((n) => vectorNodeBounds(n.node!, multiplyMatrices(n.parent, unrotate))));\n\treturn b ? { ...b, angle } : null;\n}\n\nconst normalizeAngle = (a: number) => ((((a + 180) % 360) + 360) % 360) - 180;\n\n/** Screen positions of the selection box handles for a box given in screen pixels. */\nexport function getTransformHandlePositions(box: VectorBounds): { handle: VectorTransformHandle; x: number; y: number }[] {\n\tconst { x, y, width: w, height: h } = box;\n\treturn [\n\t\t{ handle: 'nw', x, y },\n\t\t{ handle: 'n', x: x + w / 2, y },\n\t\t{ handle: 'ne', x: x + w, y },\n\t\t{ handle: 'e', x: x + w, y: y + h / 2 },\n\t\t{ handle: 'se', x: x + w, y: y + h },\n\t\t{ handle: 's', x: x + w / 2, y: y + h },\n\t\t{ handle: 'sw', x, y: y + h },\n\t\t{ handle: 'w', x, y: y + h / 2 },\n\t\t{ handle: 'rotate', x: x + w / 2, y: y - VECTOR_ROTATE_HANDLE_OFFSET }\n\t];\n}\n\n/**\n * Screen geometry of a selection box. `box` and each handle's `x / y` are the unrotated box\n * in screen pixels; draw them inside a group with `transform` (a `rotate(angle cx cy)`),\n * or use `screen` for the true on-screen positions (hit-testing).\n */\nexport interface VectorSelectionBoxGeometry {\n\tbox: VectorBounds;\n\tangle: number;\n\ttransform: string;\n\thandles: { handle: VectorTransformHandle; x: number; y: number; screen: Vec2 }[];\n\t/** The four corners on screen, clockwise from the top left. */\n\tcorners: Vec2[];\n}\n\nexport function getVectorSelectionBoxGeometry(frame: VectorSelectionFrame, viewport: VectorViewport): VectorSelectionBoxGeometry {\n\tconst z = viewport.zoom;\n\tconst box = { x: frame.x * z + viewport.x, y: frame.y * z + viewport.y, width: frame.width * z, height: frame.height * z };\n\tconst rot = rotateMatrix(frame.angle, { x: viewport.x, y: viewport.y });\n\tconst toScreen = (p: Vec2): Vec2 => (frame.angle ? applyMatrix(rot, p) : { x: p.x, y: p.y });\n\tconst handles = getTransformHandlePositions(box).map((h) => ({ ...h, screen: toScreen(h) }));\n\tconst corners = [\n\t\t{ x: box.x, y: box.y },\n\t\t{ x: box.x + box.width, y: box.y },\n\t\t{ x: box.x + box.width, y: box.y + box.height },\n\t\t{ x: box.x, y: box.y + box.height }\n\t].map(toScreen);\n\tconst r = (n: number) => Math.round(n * 1000) / 1000;\n\treturn { box, angle: frame.angle, transform: frame.angle ? `rotate(${r(frame.angle)} ${r(viewport.x)} ${r(viewport.y)})` : '', handles, corners };\n}\n\nconst RESIZE_CURSOR_ORDER = ['ew-resize', 'nwse-resize', 'ns-resize', 'nesw-resize'];\nconst HANDLE_ANGLES: Partial<Record<VectorTransformHandle, number>> = { e: 0, se: 45, s: 90, sw: 135, w: 180, nw: 225, n: 270, ne: 315 };\n\n/** The resize cursor for a handle of a box rotated by `angle` degrees. */\nexport function vectorResizeCursor(handle: VectorTransformHandle, angle = 0): string {\n\tif (handle === 'rotate') return 'grab';\n\tconst a = (((HANDLE_ANGLES[handle]! + angle) % 180) + 180) % 180;\n\treturn RESIZE_CURSOR_ORDER[Math.round(a / 45) % 4];\n}\n\n\nconst TOOL_LABELS: Record<VectorTool, string> = {\n\tselect: 'Select',\n\tdirect: 'Direct Select',\n\tpen: 'Pen',\n\tpencil: 'Pencil',\n\trect: 'Rectangle',\n\tellipse: 'Ellipse',\n\tpolygon: 'Polygon',\n\tstar: 'Star',\n\tline: 'Line',\n\tscissors: 'Scissors',\n\tknife: 'Knife',\n\teraser: 'Eraser',\n\twidth: 'Width',\n\tshapeBuilder: 'Shape Builder',\n\tpaintBucket: 'Paint Bucket',\n\tblend: 'Blend'\n};\n\nconst TOOL_KEYS: Record<string, VectorTool> = {\n\tv: 'select',\n\ta: 'direct',\n\tp: 'pen',\n\tn: 'pencil',\n\tr: 'rect',\n\to: 'ellipse',\n\ty: 'polygon',\n\ts: 'star',\n\tl: 'line',\n\tc: 'scissors',\n\tk: 'knife',\n\te: 'eraser',\n\tw: 'width',\n\tm: 'shapeBuilder',\n\tb: 'paintBucket',\n\tj: 'blend'\n};\n\nexport function vectorToolLabel(tool: VectorTool): string {\n\treturn TOOL_LABELS[tool];\n}\n\n/** Keyboard shortcut of each tool (single letters, no modifiers). */\nexport const VECTOR_TOOL_SHORTCUTS: Readonly<Record<VectorTool, string>> = {\n\tselect: 'V',\n\tdirect: 'A',\n\tpen: 'P',\n\tpencil: 'N',\n\trect: 'R',\n\tellipse: 'O',\n\tpolygon: 'Y',\n\tstar: 'S',\n\tline: 'L',\n\tscissors: 'C',\n\tknife: 'K',\n\teraser: 'E',\n\twidth: 'W',\n\tshapeBuilder: 'M',\n\tpaintBucket: 'B',\n\tblend: 'J'\n};\n\n/** Short glyphs for tool buttons (pair them with `vectorToolLabel` for the accessible name). */\nexport const VECTOR_TOOL_GLYPHS: Readonly<Record<VectorTool, string>> = {\n\tselect: '↖',\n\tdirect: '⌖',\n\tpen: '✒',\n\tpencil: '✎',\n\trect: '▭',\n\tellipse: '◯',\n\tpolygon: '⬡',\n\tstar: '☆',\n\tline: '╱',\n\tscissors: '✂',\n\tknife: '⟋',\n\teraser: '⌫',\n\twidth: '⇹',\n\tshapeBuilder: '⊕',\n\tpaintBucket: '◍',\n\tblend: '⧓'\n};\n\n/** Tools that drag out a shape (they snap while hovering). */\nconst SHAPE_TOOLS: ReadonlySet<VectorTool> = new Set(['rect', 'ellipse', 'polygon', 'star', 'line']);\n\nconst BOOLEAN_OPS: Record<VectorBooleanOperation, 'union' | 'difference' | 'intersection' | 'xor'> = {\n\tunion: 'union',\n\tsubtract: 'difference',\n\tintersect: 'intersection',\n\texclude: 'xor'\n};\n\nconst BOOLEAN_LABELS: Record<VectorBooleanOperation, string> = { union: 'Union', subtract: 'Subtract', intersect: 'Intersect', exclude: 'Exclude' };\n\n/** Names of the Pathfinder operations (undo steps, groups and buttons). */\nexport const VECTOR_PATHFINDER_LABELS: Readonly<Record<VectorPathfinderOperation, string>> = {\n\tdivide: 'Divide',\n\ttrim: 'Trim',\n\tmerge: 'Merge',\n\tcrop: 'Crop',\n\toutline: 'Outline',\n\tminusBack: 'Minus Back'\n};\n\n/** Replaces nodes by id anywhere in the tree (structural sharing for untouched branches). */\nfunction mapTree(nodes: readonly VectorNode[], fn: (node: VectorNode) => VectorNode | null): VectorNode[] {\n\tlet changed = false;\n\tconst out: VectorNode[] = [];\n\tfor (const node of nodes) {\n\t\tlet next = fn(node);\n\t\tif (next && next.type === 'group') {\n\t\t\tconst children = mapTree(next.children, fn);\n\t\t\tif (children !== next.children) next = { ...next, children };\n\t\t}\n\t\tif (next !== node) changed = true;\n\t\tif (next) out.push(next);\n\t}\n\treturn changed ? out : (nodes as VectorNode[]);\n}\n\n/** Replaces node `id` anywhere in the tree with `replacements` (none removes it). */\nfunction spliceTree(nodes: readonly VectorNode[], id: string, replacements: readonly VectorNode[]): VectorNode[] {\n\tlet changed = false;\n\tconst out: VectorNode[] = [];\n\tfor (const node of nodes) {\n\t\tif (node.id === id) {\n\t\t\tchanged = true;\n\t\t\tout.push(...replacements);\n\t\t\tcontinue;\n\t\t}\n\t\tif (node.type === 'group') {\n\t\t\tconst children = spliceTree(node.children, id, replacements);\n\t\t\tif (children !== node.children) {\n\t\t\t\tchanged = true;\n\t\t\t\tout.push({ ...node, children });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tout.push(node);\n\t}\n\treturn changed ? out : (nodes as VectorNode[]);\n}\n\nconst toPoint = (p: Vec2): Point => [p.x, p.y];\n\n/** A node's place and state (transform, visibility, lock, operand operation), for a node taking its place. */\nconst placementOf = (n: VectorNode) => ({\n\t...(n.transform ? { transform: n.transform } : {}),\n\t...(n.visible === false ? { visible: false } : {}),\n\t...(n.locked ? { locked: true } : {}),\n\t...(n.booleanOperation ? { booleanOperation: n.booleanOperation } : {})\n});\n\nconst isRoot = (m: AffineMatrix) => m === IDENTITY_MATRIX || (m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0);\n\nlet idCounter = 0;\nconst defaultCreateId = () => `vn-${Date.now().toString(36)}${(++idCounter).toString(36)}`;\n\n// ---------------------------------------------------------------------------\n// Controller\n// ---------------------------------------------------------------------------\n\ninterface Snapshot {\n\tdoc: VectorDocument;\n\tselection: string[];\n\tanchors: VectorAnchorRef[];\n\teditingId: string | null;\n\tpen: { nodeId: string; subpath: number } | null;\n}\n\n/** Snap targets of a drag, gathered on its first move that needs them. */\ntype DragTargets = { targets?: VectorSnapTargets };\n\ntype Drag =\n\t| ({ kind: 'move'; start: Vec2; origin: VectorBounds | null; before: Snapshot; base: VectorDocument; moved: boolean; duplicated: boolean; clickId: string | null } & DragTargets)\n\t| ({ kind: 'scale'; handle: VectorTransformHandle; start: Vec2; frame: VectorSelectionFrame; before: Snapshot; base: VectorDocument } & DragTargets)\n\t| { kind: 'rotate'; start: Vec2; center: Vec2; before: Snapshot; base: VectorDocument }\n\t| { kind: 'marquee'; start: Vec2; base: string[]; baseAnchors: VectorAnchorRef[]; anchorsMode: boolean }\n\t| ({ kind: 'anchors'; start: Vec2; before: Snapshot; base: VectorDocument; moved: boolean; clickRef: VectorAnchorRef | null; lead: VectorAnchorRef | null } & DragTargets)\n\t| ({ kind: 'handle'; ref: VectorHandleRef; before: Snapshot; base: VectorDocument } & DragTargets)\n\t| { kind: 'bend'; ref: VectorSegmentRef; t: number; start: Vec2; before: Snapshot; base: VectorDocument; moved: boolean }\n\t| { kind: 'corner'; widget: VectorCornerWidget; refs: VectorAnchorRef[]; start: Vec2; before: Snapshot; base: VectorDocument; moved: boolean; alt: boolean; shift: boolean }\n\t| { kind: 'stroke'; tool: 'knife' | 'eraser'; points: Vec2[]; before: Snapshot }\n\t| { kind: 'builder'; erase: boolean; points: Vec2[]; touched: VectorBuilderTouched; map: VectorShapeBuilderMap }\n\t| { kind: 'pull'; ref: VectorAnchorRef; before: Snapshot; base: VectorDocument; label: string; outOnly: boolean; closing: boolean; altBreaks: boolean }\n\t| ({ kind: 'shape'; tool: VectorTool; start: Vec2; nodeId: string; before: Snapshot } & DragTargets)\n\t| { kind: 'pencil'; nodeId: string; points: Point[]; before: Snapshot; lazy: LazyMouse; pressures: number[] | null; pressure: number }\n\t| {\n\t\t\tkind: 'width';\n\t\t\tref: VectorWidthPointRef;\n\t\t\t/** `copy`: Alt-drag a centre to slide a copy of the point. */\n\t\t\tmode: 'width' | 'move' | 'copy';\n\t\t\tside: 'both' | 'left' | 'right';\n\t\t\t/** The index the drag started on (the source of a copy). */\n\t\t\tsource: number;\n\t\t\t/** Other selected points of the path that move or widen with it (indices into `profile`). */\n\t\t\tgroup: number[];\n\t\t\t/** Shift-click (adding the point): a click without a move keeps the other selected points. */\n\t\t\tshift: boolean;\n\t\t\t/** Old index → new index after a group move re-sorted the profile. */\n\t\t\trenumber: Map<number, number> | null;\n\t\t\t/** The stroke line's point and left normal where the drag started (node space). */\n\t\t\torigin: Vec2;\n\t\t\tnormal: Vec2;\n\t\t\t/** Half the drawn stroke width: side scale 1 reaches this far. */\n\t\t\tunit: number;\n\t\t\tprofile: VectorWidthPoint[];\n\t\t\tbefore: Snapshot;\n\t\t\tbase: VectorDocument;\n\t\t\tstart: Vec2;\n\t\t\tmoved: boolean;\n\t\t\tcreated: boolean;\n\t }\n\t| { kind: 'pan'; start: Vec2; viewport: VectorViewport };\n\n/**\n * Headless state for a vector editor: a document of paths and groups, select / direct /\n * pen / pencil / shape tools driven by pointer and key events, undo, transforms, boolean\n * operations, alignment, grouping, SVG import / export and a viewport. Any UI forwards\n * pointer events (in canvas pixels) and renders `getState()`; `getState()` returns the same\n * object until something changes, so it works with `useSyncExternalStore` and Svelte runes.\n */\nexport class VectorEditorController {\n\treadonly history: HistoryManager;\n\tprivate doc: VectorDocument;\n\tprivate selection: string[] = [];\n\tprivate anchors: VectorAnchorRef[] = [];\n\tprivate widthPoints: VectorWidthPointRef[] = [];\n\tprivate widthEdit: VectorWidthPointEdit | null = null;\n\tprivate editingId: string | null = null;\n\tprivate pen: { nodeId: string; subpath: number } | null = null;\n\tprivate tool: VectorTool;\n\tprivate defaults: VectorEditorState['defaults'];\n\tprivate viewport: VectorViewport;\n\tprivate snap: VectorSnapOptions;\n\tprivate editing: VectorPathEditOptions;\n\tprivate hitRadius: number;\n\tprivate hover: VectorHover | null = null;\n\tprivate pointer: Vec2 | null = null;\n\tprivate drag: Drag | null = null;\n\tprivate interaction: VectorInteraction | null = null;\n\tprivate guides: VectorSnapGuides | null = null;\n\t/** Whether the last `snapDrawn` landed on an object (guides may be hidden). */\n\tprivate snappedToObject = false;\n\t/** Drawing targets for hovering the pen and shape tools, kept while the document and the excluded path stay the same. */\n\tprivate hoverTargets: { doc: VectorDocument; exclude: string; targets: VectorSnapTargets } | null = null;\n\tprivate spaceHeld = false;\n\tprivate clipboard: VectorNode[] | null = null;\n\t/** Shape Builder map of the selection, kept while the document and the selection stay the same. */\n\tprivate builderCache: { doc: VectorDocument; key: string; map: VectorShapeBuilderMap | null } | null = null;\n\t/** Paint bucket map of the visible outlines. */\n\tprivate fillCache: { doc: VectorDocument; gap: number; map: PlanarMap | null } | null = null;\n\tprivate builderPreview: VectorBuilderPreview | null = null;\n\tprivate blendStart: VectorBlendStart | null = null;\n\t/** Options new blends get (the toolbars edit them along with the selected blend's). */\n\tprivate blendDefaults: VectorBlendOptions = {};\n\t/** What the highlight shows, to skip notifying when the pointer stays over the same face. */\n\tprivate builderHoverKey: string | null = null;\n\tprivate readonly createId: () => string;\n\tprivate readonly onChange?: (document: VectorDocument) => void;\n\tprivate readonly listeners = new Set<(state: VectorEditorState) => void>();\n\tprivate readonly cleanups: Array<() => void> = [];\n\tprivate applying = false;\n\t/** Set while a commit records a change that was already applied, so `restore` still reports it. */\n\tprivate committing = false;\n\tprivate revision = 0;\n\tprivate state!: VectorEditorState;\n\n\tconstructor(options: VectorEditorOptions = {}) {\n\t\tthis.history = options.history ?? new HistoryManager();\n\t\tthis.doc = options.document ? cloneVectorDocument(options.document) : { width: 800, height: 600, nodes: [] };\n\t\tthis.doc = this.refreshed(this.doc);\n\t\tthis.tool = options.tool ?? 'select';\n\t\tthis.defaults = { ...DEFAULT_DEFAULTS, ...(options.defaults ?? {}) } as VectorEditorState['defaults'];\n\t\tthis.viewport = { x: 0, y: 0, zoom: 1, ...(options.viewport ?? {}) };\n\t\tthis.snap = { ...DEFAULT_VECTOR_SNAP, ...(options.snap ?? {}) };\n\t\tthis.editing = { ...DEFAULT_VECTOR_PATH_EDIT, ...(options.editing ?? {}) };\n\t\tthis.hitRadius = options.hitRadius ?? 6;\n\t\tthis.createId = options.createId ?? defaultCreateId;\n\t\tthis.onChange = options.onChange;\n\t\tthis.state = this.computeState();\n\t\tthis.cleanups.push(\n\t\t\tthis.history.subscribe((h) => {\n\t\t\t\tconst s = this.state;\n\t\t\t\tif (this.applying || (s.canUndo === h.canUndo && s.canRedo === h.canRedo && s.undoLabel === h.undoName && s.redoLabel === h.redoName)) return;\n\t\t\t\tthis.notify();\n\t\t\t})\n\t\t);\n\t}\n\n\t/** Stops listening to the history. */\n\tdestroy(): void {\n\t\tthis.cleanups.splice(0).forEach((fn) => fn());\n\t\tthis.listeners.clear();\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// State\n\t// -------------------------------------------------------------------------\n\n\t/** The current snapshot (stable until the next change). */\n\tgetState = (): VectorEditorState => this.state;\n\n\t/** Calls `listener` after every change. Returns an unsubscribe function. */\n\tsubscribe = (listener: (state: VectorEditorState) => void): (() => void) => {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t};\n\n\tgetDocument(): VectorDocument {\n\t\treturn this.doc;\n\t}\n\n\tgetNode(id: string): VectorNode | undefined {\n\t\treturn findVectorNode(this.doc.nodes, id);\n\t}\n\n\tprivate computeState(): VectorEditorState {\n\t\tconst h = this.history.getState();\n\t\treturn {\n\t\t\tdocument: this.doc,\n\t\t\ttool: this.tool,\n\t\t\tselection: this.selection,\n\t\t\tselectedAnchors: this.anchors,\n\t\t\tselectedWidthPoints: this.widthPoints,\n\t\t\twidthPointEdit: this.widthEdit,\n\t\t\teditingId: this.editingId,\n\t\t\tselectionBounds: this.boundsOf(this.selection),\n\t\t\tselectionFrame: this.frameOf(this.selectedRoots()),\n\t\t\tviewport: this.viewport,\n\t\t\tdefaults: this.defaults,\n\t\t\tsnap: this.snap,\n\t\t\tediting: this.editing,\n\t\t\tguides: this.guides,\n\t\t\thitRadius: this.hitRadius,\n\t\t\thover: this.hover,\n\t\t\tcursor: this.cursorFor(),\n\t\t\tinteraction: this.interaction,\n\t\t\tpen: this.pen,\n\t\t\tpointer: this.pointer,\n\t\t\tbuilder: this.builderPreview,\n\t\t\tblendStart: this.blendStart,\n\t\t\tblendDefaults: this.blendDefaults,\n\t\t\tcanUndo: h.canUndo,\n\t\t\tcanRedo: h.canRedo,\n\t\t\tundoLabel: h.undoName,\n\t\t\tredoLabel: h.redoName,\n\t\t\trevision: this.revision\n\t\t};\n\t}\n\n\t/** `doc` with its blends' steps brought up to date (the same object when they already are). */\n\tprivate refreshed(doc: VectorDocument): VectorDocument {\n\t\tconst nodes = refreshVectorBlends(doc.nodes);\n\t\treturn nodes === doc.nodes ? doc : { ...doc, nodes };\n\t}\n\n\tprivate notify(docChanged = false) {\n\t\t// Blends regenerate their steps after every edit of a key (drags included).\n\t\tconst fresh = this.refreshed(this.doc);\n\t\tif (fresh !== this.doc) this.doc = fresh;\n\t\tif (this.blendStart && !this.getNode(this.blendStart.nodeId)) this.blendStart = null;\n\t\tthis.revision++;\n\t\tthis.state = this.computeState();\n\t\tif (docChanged) this.onChange?.(this.doc);\n\t\tconst state = this.state;\n\t\tthis.listeners.forEach((listener) => {\n\t\t\ttry {\n\t\t\t\tlistener(state);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[VectorEditorController] Error in state listener:', error);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate boundsOf(ids: readonly string[]): VectorBounds | null {\n\t\treturn unionBounds(\n\t\t\tids.map((id) => {\n\t\t\t\tconst n = this.getNode(id);\n\t\t\t\treturn n ? vectorNodeBounds(n, vectorParentMatrix(this.doc.nodes, id) ?? IDENTITY_MATRIX) : null;\n\t\t\t})\n\t\t);\n\t}\n\n\tprivate frameOf(ids: readonly string[]): VectorSelectionFrame | null {\n\t\treturn getVectorSelectionFrame(this.doc, ids);\n\t}\n\n\t/** Local → document matrix of a node in `doc` (default: the current document). */\n\tprivate worldOf(id: string, doc: VectorDocument = this.doc): AffineMatrix {\n\t\treturn vectorWorldMatrix(doc.nodes, id) ?? IDENTITY_MATRIX;\n\t}\n\n\t/** A document point in node `id`'s own space. */\n\tprivate toLocal(id: string, p: Vec2, doc: VectorDocument = this.doc): Vec2 {\n\t\treturn applyMatrix(invertMatrix(this.worldOf(id, doc)), p);\n\t}\n\n\tprivate cursorFor(): string {\n\t\tif (this.drag?.kind === 'pan' || this.spaceHeld) return this.drag ? 'grabbing' : 'grab';\n\t\tif (this.drag?.kind === 'rotate') return 'grabbing';\n\t\tif (this.drag?.kind === 'move') return 'move';\n\t\tconst h = this.hover;\n\t\tswitch (this.tool) {\n\t\t\tcase 'select':\n\t\t\t\tif (h?.transformHandle) return vectorResizeCursor(h.transformHandle, this.frameOf(this.selectedRoots())?.angle ?? 0);\n\t\t\t\treturn h?.nodeId ? 'move' : 'default';\n\t\t\tcase 'direct':\n\t\t\t\tif (h?.anchor || h?.handle || h?.corner) return 'pointer';\n\t\t\t\tif (h?.segment) return 'copy';\n\t\t\t\treturn h?.nodeId ? 'move' : 'default';\n\t\t\tcase 'pen':\n\t\t\t\treturn h?.anchor ? 'pointer' : 'crosshair';\n\t\t\tcase 'width':\n\t\t\t\tif (this.drag?.kind === 'width') return this.drag.mode === 'width' ? 'ew-resize' : 'grabbing';\n\t\t\t\tif (h?.widthPoint) return h.widthPoint.part === 'center' ? 'grab' : 'ew-resize';\n\t\t\t\treturn h?.strokeSpot ? 'copy' : 'default';\n\t\t\tcase 'shapeBuilder':\n\t\t\t\tif (!this.builderPreview?.faces.length && !this.builderPreview?.lines.length) return 'crosshair';\n\t\t\t\treturn this.builderPreview.mode === 'erase' ? 'not-allowed' : 'cell';\n\t\t\tcase 'paintBucket':\n\t\t\t\treturn this.builderPreview ? 'cell' : 'crosshair';\n\t\t\tcase 'blend':\n\t\t\t\treturn h?.nodeId ? (this.blendStart && this.blendStart.nodeId !== h.nodeId ? 'copy' : 'pointer') : 'crosshair';\n\t\t\tdefault:\n\t\t\t\treturn 'crosshair';\n\t\t}\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Undoable changes\n\t// -------------------------------------------------------------------------\n\n\tprivate snapshot(): Snapshot {\n\t\treturn { doc: this.doc, selection: this.selection, anchors: this.anchors, editingId: this.editingId, pen: this.pen };\n\t}\n\n\tprivate restore(s: Snapshot) {\n\t\tconst docChanged = s.doc !== this.doc || this.committing;\n\t\t// Undo / redo can renumber width points: drop the ones that are gone and close the editor.\n\t\tif (!this.committing) this.widthEdit = null;\n\t\tthis.committing = false;\n\t\tthis.applying = true;\n\t\ttry {\n\t\t\tthis.doc = s.doc;\n\t\t\tthis.selection = s.selection.filter((id) => this.getNode(id));\n\t\t\tthis.anchors = s.anchors.filter((r) => getAnchor(this.doc, r));\n\t\t\tthis.editingId = s.editingId && this.getNode(s.editingId) ? s.editingId : null;\n\t\t\tthis.pen = s.pen && (this.getNode(s.pen.nodeId) as VectorPathNode | undefined)?.subpaths?.[s.pen.subpath] ? s.pen : null;\n\t\t\tif (this.widthPoints.length) {\n\t\t\t\tconst kept = this.widthPoints.filter((r) => {\n\t\t\t\t\tconst n = this.getNode(r.nodeId);\n\t\t\t\t\treturn n?.type === 'path' && !!n.strokeWidthProfile?.[r.index];\n\t\t\t\t});\n\t\t\t\tif (kept.length !== this.widthPoints.length) this.widthPoints = kept;\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.applying = false;\n\t\t}\n\t\tthis.notify(docChanged);\n\t}\n\n\t/**\n\t * Records the change from `before` to the current state as one undo step. Consecutive\n\t * changes with the same `merge` key (a slider drag) fold into one step.\n\t */\n\tprivate commit(name: string, before: Snapshot, merge?: string) {\n\t\tthis.doc = this.refreshed(this.doc);\n\t\tconst after = this.snapshot();\n\t\tif (before.doc === after.doc) {\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst command: Command = {\n\t\t\tname,\n\t\t\texecute: () => this.restore(after),\n\t\t\tundo: () => this.restore(before),\n\t\t\tredo: () => this.restore(after),\n\t\t\t...(merge ? { tag: merge } : {})\n\t\t};\n\t\tthis.committing = true;\n\t\tif (merge) this.history.executeMerged(command, { key: merge });\n\t\telse this.history.execute(command);\n\t\tthis.committing = false;\n\t}\n\n\t/** Runs `fn` (which updates fields) and records it as one undo step (merged by `merge` key). */\n\tprivate change(name: string, fn: () => void, merge?: string): void {\n\t\tconst before = this.snapshot();\n\t\tfn();\n\t\tthis.commit(name, before, merge);\n\t}\n\n\t/** Ends a merged edit (a slider released): the next change starts a new undo step. */\n\tendMergedEdit(): void {\n\t\tthis.history.endGesture();\n\t}\n\n\tprivate setDoc(nodes: VectorNode[]) {\n\t\tif (nodes !== this.doc.nodes) this.doc = { ...this.doc, nodes };\n\t}\n\n\tprivate replaceNode(id: string, next: VectorNode | null) {\n\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === id ? next : n)));\n\t}\n\n\tprivate freshIds<T extends VectorNode>(node: T): T {\n\t\tconst copy = cloneVectorNode(node);\n\t\tconst used = new Set<string>();\n\t\twalkVectorNodes(this.doc.nodes, (n) => used.add(n.id));\n\t\tconst next = () => {\n\t\t\tlet id = this.createId();\n\t\t\twhile (used.has(id)) id = this.createId();\n\t\t\tused.add(id);\n\t\t\treturn id;\n\t\t};\n\t\tconst assign = (n: VectorNode) => {\n\t\t\tn.id = next();\n\t\t\tif (n.type === 'group') n.children.forEach(assign);\n\t\t};\n\t\tassign(copy);\n\t\treturn copy;\n\t}\n\n\tprivate newId(): string {\n\t\tlet id = this.createId();\n\t\twhile (this.getNode(id)) id = this.createId();\n\t\treturn id;\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Viewport\n\t// -------------------------------------------------------------------------\n\n\tscreenToDocument(p: Vec2): Vec2 {\n\t\tconst v = this.viewport;\n\t\treturn { x: (p.x - v.x) / v.zoom, y: (p.y - v.y) / v.zoom };\n\t}\n\n\tdocumentToScreen(p: Vec2): Vec2 {\n\t\tconst v = this.viewport;\n\t\treturn { x: p.x * v.zoom + v.x, y: p.y * v.zoom + v.y };\n\t}\n\n\tsetViewport(viewport: Partial<VectorViewport>): void {\n\t\tconst next = { ...this.viewport, ...viewport };\n\t\tnext.zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, next.zoom));\n\t\tif (next.x === this.viewport.x && next.y === this.viewport.y && next.zoom === this.viewport.zoom) return;\n\t\tthis.viewport = next;\n\t\tthis.notify();\n\t}\n\n\t/** Zooms by `factor` keeping the document point under `screen` in place. */\n\tzoomAt(screen: Vec2, factor: number): void {\n\t\tconst zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, this.viewport.zoom * factor));\n\t\tconst doc = this.screenToDocument(screen);\n\t\tthis.setViewport({ zoom, x: screen.x - doc.x * zoom, y: screen.y - doc.y * zoom });\n\t}\n\n\tpanBy(dx: number, dy: number): void {\n\t\tthis.setViewport({ x: this.viewport.x + dx, y: this.viewport.y + dy });\n\t}\n\n\t/** Fits the artboard (or `bounds`) into a canvas of `size` screen pixels. */\n\tzoomToFit(size: { width: number; height: number }, options: { padding?: number; bounds?: VectorBounds } = {}): void {\n\t\tconst padding = options.padding ?? 40;\n\t\tconst b = options.bounds ?? { x: 0, y: 0, width: this.doc.width, height: this.doc.height };\n\t\tconst zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, Math.min((size.width - padding * 2) / (b.width || 1), (size.height - padding * 2) / (b.height || 1))));\n\t\tthis.setViewport({ zoom, x: (size.width - b.width * zoom) / 2 - b.x * zoom, y: (size.height - b.height * zoom) / 2 - b.y * zoom });\n\t}\n\n\t/** Zoom at the canvas centre-ish: positive `delta` zooms out (wheel convention). Use with `wheel`. */\n\twheel(screen: Vec2, delta: { x: number; y: number }, mods: VectorModifiers = {}): void {\n\t\tif (mods.mod) this.zoomAt(screen, Math.exp(-delta.y * 0.01));\n\t\telse this.panBy(-(mods.shift ? delta.y : delta.x), -(mods.shift ? 0 : delta.y));\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Settings\n\t// -------------------------------------------------------------------------\n\n\tsetTool(tool: VectorTool): void {\n\t\tif (tool === this.tool) return;\n\t\tthis.finishPath();\n\t\tthis.cancelDrag();\n\t\tthis.tool = tool;\n\t\tthis.hover = null;\n\t\tthis.guides = null;\n\t\tthis.widthPoints = [];\n\t\tthis.widthEdit = null;\n\t\tthis.builderPreview = null;\n\t\tthis.builderHoverKey = null;\n\t\tthis.blendStart = null;\n\t\tif (tool !== 'direct') {\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t}\n\t\tthis.notify();\n\t}\n\n\tsetDefaults(defaults: Partial<VectorDefaults>): void {\n\t\tthis.defaults = { ...this.defaults, ...defaults };\n\t\tthis.notify();\n\t}\n\n\tsetSnap(snap: Partial<VectorSnapOptions>): void {\n\t\tthis.snap = { ...this.snap, ...snap };\n\t\tif (!this.snap.guides || !this.snap.objects) this.guides = null;\n\t\tthis.notify();\n\t}\n\n\t/** Changes how deleting, dragging anchors, the pencil and the eraser behave. */\n\tsetEditing(editing: Partial<VectorPathEditOptions>): void {\n\t\tconst next = { ...this.editing, ...editing };\n\t\tnext.pencilSmoothing = Math.min(1, Math.max(0, Number.isFinite(next.pencilSmoothing) ? next.pencilSmoothing : 0));\n\t\tif (!(next.eraserSize > 0)) next.eraserSize = this.editing.eraserSize;\n\t\tthis.editing = next;\n\t\tthis.notify();\n\t}\n\n\t/** Turns object snapping on or off (the toolbar's snapping toggle). */\n\ttoggleSnapping(on: boolean = !this.snap.objects): void {\n\t\tthis.setSnap({ objects: on });\n\t}\n\n\tprivate gridValue(v: number): number {\n\t\tconst g = this.snap.grid;\n\t\treturn g > 0 ? Math.round(v / g) * g : v;\n\t}\n\n\tprivate snapPoint(p: Vec2): Vec2 {\n\t\treturn { x: this.gridValue(p.x), y: this.gridValue(p.y) };\n\t}\n\n\t/** The pull distance in document units. */\n\tprivate snapTolerance(): number {\n\t\treturn this.snap.threshold / this.viewport.zoom;\n\t}\n\n\tprivate setGuides(guides: VectorSnapGuides | null) {\n\t\tthis.guides = guides && this.snap.guides && (guides.lines.length || guides.labels.length) ? guides : null;\n\t}\n\n\t/**\n\t * Where a drawn or dragged point `p` goes: onto the smart guide `targets` (gathered by\n\t * `gather` when missing), else onto the grid, kept to `leave`'s constraint. Cmd / Ctrl\n\t * bypasses snapping.\n\t */\n\tprivate snapDrawn(p: Vec2, leave: VectorSnapLeave | null, input: VectorModifiers, targets: () => VectorSnapTargets): Vec2 {\n\t\tthis.snappedToObject = false;\n\t\tif (input.mod) {\n\t\t\tthis.setGuides(null);\n\t\t\treturn constrainToLeave(leave, p);\n\t\t}\n\t\tif (this.snap.objects) {\n\t\t\tconst withAngles = leave ? { ...leave, angles: this.snap.angles } : null;\n\t\t\tconst res = targets().snapFrom(p, withAngles, this.snapTolerance());\n\t\t\tif (res.kind) {\n\t\t\t\tthis.snappedToObject = true;\n\t\t\t\tthis.setGuides(res);\n\t\t\t\t// An axis lined up with nothing still goes to the grid.\n\t\t\t\treturn leave?.constrain ? res.point : { x: res.x ? res.point.x : this.gridValue(res.point.x), y: res.y ? res.point.y : this.gridValue(res.point.y) };\n\t\t\t}\n\t\t}\n\t\tthis.setGuides(null);\n\t\treturn constrainToLeave(leave, this.snapPoint(p));\n\t}\n\n\t/** Drawing targets for the pen and shape tools, reused while the document stays the same. */\n\tprivate drawingTargets(exclude: string | null): VectorSnapTargets {\n\t\tconst key = exclude ?? '';\n\t\tconst cached = this.hoverTargets;\n\t\tif (cached && cached.doc === this.doc && cached.exclude === key) return cached.targets;\n\t\tconst targets = VectorSnapTargets.forDrawing(this.doc, exclude ? [exclude] : []);\n\t\tthis.hoverTargets = { doc: this.doc, exclude: key, targets };\n\t\treturn targets;\n\t}\n\n\t/** Where the pointer at `p` would place a point with the pen or a shape tool (and the guides showing why). */\n\tprivate hoverSnap(p: Vec2, input: VectorModifiers): Vec2 {\n\t\tif (this.tool === 'pen') {\n\t\t\tconst sp = this.pen ? (this.getNode(this.pen.nodeId) as VectorPathNode | undefined)?.subpaths[this.pen.subpath] : undefined;\n\t\t\tif (this.pen && sp && this.hover?.anchor) {\n\t\t\t\tthis.setGuides(null);\n\t\t\t\treturn p;\n\t\t\t}\n\t\t\tconst last = this.pen && sp ? getAnchorWorld(this.doc, { nodeId: this.pen.nodeId, subpath: this.pen.subpath, index: sp.anchors.length - 1 }) : undefined;\n\t\t\tconst leave = last ? { at: { x: last.x, y: last.y }, constrain: input.shift ? ('angle' as const) : null } : null;\n\t\t\treturn this.snapDrawn(p, leave, input, () => this.drawingTargets(this.pen?.nodeId ?? null));\n\t\t}\n\t\tif (SHAPE_TOOLS.has(this.tool)) return this.snapDrawn(p, null, input, () => this.drawingTargets(null));\n\t\tthis.setGuides(null);\n\t\treturn p;\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Document\n\t// -------------------------------------------------------------------------\n\n\t/** Replaces the document. Undoable unless `{ history: false }` (then history is cleared). */\n\tsetDocument(doc: VectorDocument, options: { history?: boolean; label?: string } = {}): void {\n\t\tthis.cancelDrag();\n\t\tconst next = cloneVectorDocument(doc);\n\t\tif (options.history === false) {\n\t\t\tthis.doc = next;\n\t\t\tthis.selection = [];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t\tthis.pen = null;\n\t\t\tthis.history.clear();\n\t\t\tthis.notify(true);\n\t\t\treturn;\n\t\t}\n\t\tthis.change(options.label ?? 'Replace Document', () => {\n\t\t\tthis.doc = next;\n\t\t\tthis.selection = [];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t\tthis.pen = null;\n\t\t});\n\t}\n\n\t/** Changes the artboard size or background. */\n\tsetArtboard(patch: Partial<Pick<VectorDocument, 'width' | 'height' | 'background'>>): void {\n\t\tthis.change('Change Artboard', () => {\n\t\t\tthis.doc = { ...this.doc, ...patch };\n\t\t});\n\t}\n\n\t/** Replaces the document with parsed SVG markup (one undo step). */\n\tloadSvg(svg: string, options: { history?: boolean } = {}): VectorActionResult {\n\t\ttry {\n\t\t\tconst doc = parseVectorSvg(svg, { createId: () => this.createId() });\n\t\t\tthis.setDocument(doc, { history: options.history, label: 'Import SVG' });\n\t\t\treturn { ok: true };\n\t\t} catch (error) {\n\t\t\treturn { ok: false, message: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t}\n\n\t/** Adds the nodes of SVG markup on top of the document, selected. */\n\timportSvg(svg: string): VectorActionResult {\n\t\ttry {\n\t\t\tconst parsed = parseVectorSvg(svg);\n\t\t\tconst nodes = parsed.nodes.map((n) => this.freshIds(n));\n\t\t\tif (!nodes.length) return { ok: false, message: 'The SVG has no shapes the editor can read.' };\n\t\t\tthis.change('Import SVG', () => {\n\t\t\t\tthis.setDoc([...this.doc.nodes, ...nodes]);\n\t\t\t\tthis.selection = nodes.map((n) => n.id);\n\t\t\t\tthis.anchors = [];\n\t\t\t});\n\t\t\treturn { ok: true };\n\t\t} catch (error) {\n\t\t\treturn { ok: false, message: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t}\n\n\ttoSvg(options?: VectorSvgExportOptions): string {\n\t\treturn vectorDocumentToSvg(this.doc, options);\n\t}\n\n\ttoJSON(): VectorDocument {\n\t\treturn cloneVectorDocument(this.doc);\n\t}\n\n\t/** Adds a node (ids are kept unless taken). Returns its id. */\n\taddNode(node: VectorNode, options: AddVectorNodeOptions = {}): string {\n\t\tconst taken = (() => {\n\t\t\tlet clash = false;\n\t\t\twalkVectorNodes([node], (n) => {\n\t\t\t\tif (this.getNode(n.id)) clash = true;\n\t\t\t});\n\t\t\treturn clash;\n\t\t})();\n\t\tconst next = taken ? this.freshIds(node) : cloneVectorNode(node);\n\t\tthis.change(options.label ?? `Add ${next.type === 'group' ? 'Group' : (next.name ?? 'Path')}`, () => {\n\t\t\tconst insert = (list: VectorNode[]) => {\n\t\t\t\tconst copy = list.slice();\n\t\t\t\tcopy.splice(options.index ?? copy.length, 0, next);\n\t\t\t\treturn copy;\n\t\t\t};\n\t\t\tif (options.parentId) {\n\t\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === options.parentId && n.type === 'group' ? { ...n, children: insert(n.children) } : n)));\n\t\t\t} else this.setDoc(insert(this.doc.nodes));\n\t\t\tif (options.select !== false) {\n\t\t\t\tthis.selection = [next.id];\n\t\t\t\tthis.anchors = [];\n\t\t\t}\n\t\t});\n\t\treturn next.id;\n\t}\n\n\t/** Builds a path node for a shape with the current defaults (not added). */\n\tcreateShapeNode(input: VectorShapeInput): VectorPathNode {\n\t\tconst b = { x: input.x, y: input.y, width: input.width, height: input.height };\n\t\tconst d = this.defaults;\n\t\tlet subpath: VectorSubpath;\n\t\tswitch (input.kind) {\n\t\t\tcase 'rect':\n\t\t\t\tsubpath = rectSubpath(b);\n\t\t\t\tbreak;\n\t\t\tcase 'ellipse':\n\t\t\t\tsubpath = ellipseSubpath(b);\n\t\t\t\tbreak;\n\t\t\tcase 'polygon':\n\t\t\t\tsubpath = polygonSubpath(b, input.sides ?? d.polygonSides);\n\t\t\t\tbreak;\n\t\t\tcase 'star':\n\t\t\t\tsubpath = starSubpath(b, input.points ?? d.starPoints, input.innerRatio ?? d.starInnerRatio);\n\t\t\t\tbreak;\n\t\t\tcase 'line':\n\t\t\t\tsubpath = lineSubpath({ x: b.x, y: b.y }, { x: b.x + b.width, y: b.y + b.height });\n\t\t\t\tbreak;\n\t\t}\n\t\tconst radius = input.cornerRadius ?? d.cornerRadius;\n\t\tconst kind = input.cornerKind ?? d.cornerKind;\n\t\t// Live corners: the shape keeps its sharp anchors, so the radius stays editable.\n\t\tif ((input.kind === 'rect' || input.kind === 'polygon' || input.kind === 'star') && radius > 0) subpath = setSubpathCorners(subpath, radius, kind);\n\t\tconst names = { rect: 'Rectangle', ellipse: 'Ellipse', polygon: 'Polygon', star: 'Star', line: 'Line' };\n\t\tconst style = this.styleFor(input.kind === 'line');\n\t\tconst own: VectorStyle = {};\n\t\tfor (const key of VECTOR_STYLE_KEYS) {\n\t\t\tif (input[key] !== undefined) (own as any)[key] = input[key];\n\t\t}\n\t\treturn { id: this.newId(), type: 'path', name: input.name ?? names[input.kind], ...style, ...own, subpaths: [subpath] };\n\t}\n\n\t/** Adds a shape drawn with the current defaults. Returns its id. */\n\taddShape(input: VectorShapeInput, options: AddVectorNodeOptions = {}): string {\n\t\tconst node = this.createShapeNode(input);\n\t\treturn this.addNode(node, { label: `Add ${node.name}`, ...options });\n\t}\n\n\t/** Adds a path from SVG path data. Returns its id. */\n\taddPath(d: string, style: VectorStyle & { name?: string } = {}, options: AddVectorNodeOptions = {}): string {\n\t\tconst subpaths = bezierToSubpaths(d);\n\t\tconst node: VectorPathNode = { id: this.newId(), type: 'path', name: style.name ?? 'Path', ...this.styleFor(false), ...style, subpaths };\n\t\treturn this.addNode(node, { label: 'Add Path', ...options });\n\t}\n\n\tprivate styleFor(open: boolean): VectorStyle {\n\t\tconst d = this.defaults;\n\t\tconst out: VectorStyle = { fill: open ? null : (d.fill ?? null), stroke: d.stroke ?? null, strokeWidth: d.strokeWidth ?? 1 };\n\t\tif (d.strokeLinecap) out.strokeLinecap = d.strokeLinecap;\n\t\tif (d.strokeLinejoin) out.strokeLinejoin = d.strokeLinejoin;\n\t\tif (d.strokeDasharray) out.strokeDasharray = d.strokeDasharray;\n\t\tif (d.fillRule) out.fillRule = d.fillRule;\n\t\tif (d.strokeAlign && d.strokeAlign !== 'center') out.strokeAlign = d.strokeAlign;\n\t\tfor (const key of ['startArrow', 'endArrow', 'arrowAlign', 'startArrowScale', 'endArrowScale'] as const) if (d[key]) (out as any)[key] = d[key];\n\t\tif (d.strokeWidthProfile?.length) out.strokeWidthProfile = d.strokeWidthProfile.map((p) => ({ ...p }));\n\t\treturn out;\n\t}\n\n\t/** Merges `patch` into a node (paths take style and geometry; groups name / visibility / opacity). */\n\tupdateNode(id: string, patch: Partial<VectorPathNode> | Partial<VectorGroupNode>, options: { label?: string } = {}): VectorActionResult {\n\t\tconst node = this.getNode(id);\n\t\tif (!node) return { ok: false, message: `There is no node \"${id}\".` };\n\t\tconst { id: _id, type: _type, ...rest } = patch as any;\n\t\tthis.change(options.label ?? 'Edit Node', () => this.replaceNode(id, { ...node, ...rest } as VectorNode));\n\t\treturn { ok: true };\n\t}\n\n\t/** Applies style (and opacity) to the selected (or given) nodes; groups pass it to every path inside. */\n\tsetStyle(style: VectorStyle & { opacity?: number }, ids: readonly string[] = this.selection, options: { label?: string } = {}): VectorActionResult {\n\t\tconst targets = new Set<string>();\n\t\tfor (const id of ids) {\n\t\t\tconst node = this.getNode(id);\n\t\t\tif (!node) return { ok: false, message: `There is no node \"${id}\".` };\n\t\t\tfor (const p of collectPathNodes(node)) targets.add(p.id);\n\t\t}\n\t\tif (!targets.size) return { ok: false, message: 'Nothing is selected.' };\n\t\tthis.change(options.label ?? 'Change Style', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (targets.has(n.id) ? withStyle(n as VectorPathNode, style) : n)));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * Sets the variable-width profile of the selected (or given) paths: a preset id\n\t * (`uniform` removes the profile), points, or a parametric taper / wave. Returns an error for\n\t * an unknown preset.\n\t */\n\tsetWidthProfile(\n\t\tprofile: VectorWidthPreset | readonly VectorWidthPoint[] | { taper?: VectorTaperOptions; wave?: VectorWaveOptions } | null,\n\t\toptions: { ids?: readonly string[] } = {}\n\t): VectorActionResult {\n\t\tlet points: VectorWidthPoint[];\n\t\tif (profile === null) points = [];\n\t\telse if (typeof profile === 'string') {\n\t\t\tconst preset = widthProfilePreset(profile);\n\t\t\tif (!preset) return { ok: false, message: `There is no width profile \"${profile}\".` };\n\t\t\tpoints = preset;\n\t\t} else if (Array.isArray(profile)) points = sanitizeWidthProfile(profile);\n\t\telse {\n\t\t\tconst p = profile as { taper?: VectorTaperOptions; wave?: VectorWaveOptions };\n\t\t\tpoints = p.taper || p.wave ? generateWidthProfile(p) : [];\n\t\t}\n\t\tthis.widthPoints = [];\n\t\tthis.widthEdit = null;\n\t\treturn this.setStyle({ strokeWidthProfile: points.length ? points : null }, options.ids ?? this.selection, { label: points.length ? 'Width Profile' : 'Uniform Width' });\n\t}\n\n\t/** Removes one width point of a path's profile. */\n\tremoveWidthPoint(ref: Pick<VectorWidthPointRef, 'nodeId' | 'index'>): VectorActionResult {\n\t\treturn this.removeWidthPoints([ref]);\n\t}\n\n\t/** Removes width points (default: the selected ones — the width tool's Delete), as one undo step. */\n\tremoveWidthPoints(refs: readonly Pick<VectorWidthPointRef, 'nodeId' | 'index'>[] = this.widthPoints): VectorActionResult {\n\t\tconst byNode = new Map<string, number[]>();\n\t\tfor (const r of refs) {\n\t\t\tconst node = this.getNode(r.nodeId);\n\t\t\tif (node?.type !== 'path' || !node.strokeWidthProfile?.[r.index]) continue;\n\t\t\tbyNode.set(r.nodeId, [...(byNode.get(r.nodeId) ?? []), r.index]);\n\t\t}\n\t\tif (!byNode.size) return { ok: false, message: 'There is no such width point.' };\n\t\tconst count = [...byNode.values()].reduce((n, l) => n + new Set(l).size, 0);\n\t\tthis.widthPoints = [];\n\t\tthis.widthEdit = null;\n\t\tthis.change(count > 1 ? 'Delete Width Points' : 'Delete Width Point', () => {\n\t\t\tfor (const [id, indices] of byNode) {\n\t\t\t\tconst node = this.getNode(id) as VectorPathNode;\n\t\t\t\tconst rest = removeWidthPoints(sanitizeWidthProfile(node.strokeWidthProfile), indices);\n\t\t\t\tthis.replaceNode(id, withStyle(node, { strokeWidthProfile: rest.length ? rest : null }));\n\t\t\t}\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Selects width points (width tool); points on other paths than the first are dropped. */\n\tselectWidthPoints(refs: readonly VectorWidthPointRef[]): void {\n\t\tconst first = refs[0]?.nodeId;\n\t\tconst next: VectorWidthPointRef[] = [];\n\t\tfor (const r of refs) {\n\t\t\tif (r.nodeId !== first || next.some((q) => q.index === r.index)) continue;\n\t\t\tconst n = this.getNode(r.nodeId);\n\t\t\tif (n?.type === 'path' && n.strokeWidthProfile?.[r.index]) next.push({ nodeId: r.nodeId, index: r.index, subpath: r.subpath ?? 0 });\n\t\t}\n\t\tthis.widthPoints = next;\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Sets one width point numerically: `index` names an existing point of path `nodeId`, null\n\t * (or left out) adds one at `values.t`. `left` / `right` are side widths in document units\n\t * (how far the stroke reaches on each side of the path), `t` the position along each subpath\n\t * (0–1). A point moved onto another one's `t` makes a step there. Selects the point and\n\t * refreshes an open width-point editor; one undo step. Ported from ArtCraft vectorcraft's\n\t * Width Point Edit dialog (`crates/tools/src/distort/width.rs`, MIT OR Apache-2.0).\n\t */\n\tsetWidthPoint(target: { nodeId: string; index?: number | null }, values: { t?: number; left?: number; right?: number }): VectorActionResult {\n\t\tconst node = this.getNode(target.nodeId);\n\t\tif (node?.type !== 'path') return { ok: false, message: 'There is no such path.' };\n\t\tconst g = vectorStrokeWidthGeometry(node);\n\t\tif (!g || !(g.unit > 0)) return { ok: false, message: 'The path has no visible stroke.' };\n\t\tconst num = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);\n\t\tconst t = num(values.t);\n\t\tconst left = num(values.left);\n\t\tconst right = num(values.right);\n\t\tconst scale = (w: number | undefined) => (w === undefined ? undefined : Math.max(0, w) / g.unit);\n\t\tconst profile = sanitizeWidthProfile(node.strokeWidthProfile);\n\t\tlet points: VectorWidthPoint[];\n\t\tlet index: number;\n\t\tconst existing = target.index ?? null;\n\t\tif (existing === null) {\n\t\t\tif (t === undefined) return { ok: false, message: 'A new width point needs a position t.' };\n\t\t\tif (profile.length >= VECTOR_WIDTH_PROFILE_MAX_POINTS) return { ok: false, message: 'The profile has too many width points.' };\n\t\t\t({ points, index } = insertWidthPoint(profile.length ? profile : null, t, { left: scale(left), right: scale(right) }, { step: true }));\n\t\t} else {\n\t\t\tconst p = profile[existing];\n\t\t\tif (!p) return { ok: false, message: 'There is no such width point.' };\n\t\t\tconst sides = { left: scale(left) ?? p.left, right: scale(right) ?? p.right };\n\t\t\tconst tt = t === undefined ? p.t : Math.min(Math.max(t, 0), 1);\n\t\t\tif (Math.abs(tt - p.t) <= VECTOR_WIDTH_SAME_T) {\n\t\t\t\tpoints = setWidthPoint(profile, existing, sides);\n\t\t\t\tindex = existing;\n\t\t\t} else {\n\t\t\t\t// Free to pass its neighbours: take it out and put it back at its new place.\n\t\t\t\tconst rest = removeWidthPoints(profile, [existing]);\n\t\t\t\tif (rest.length) ({ points, index } = insertWidthPoint(rest, tt, sides, { step: true }));\n\t\t\t\telse {\n\t\t\t\t\tpoints = [{ t: tt, ...sides }];\n\t\t\t\t\tindex = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst subpath = this.widthEdit?.nodeId === node.id ? this.widthEdit.subpath : this.widthPoints.find((r) => r.nodeId === node.id)?.subpath ?? 0;\n\t\tconst edit = this.widthEdit;\n\t\tthis.change('Width Point', () => this.replaceNode(node.id, withStyle(node, { strokeWidthProfile: points })));\n\t\tthis.widthPoints = [{ nodeId: node.id, index, subpath }];\n\t\tif (edit?.nodeId === node.id && (edit.index === existing || existing === null)) this.widthEdit = this.widthEditFor(node.id, subpath, index, null);\n\t\tthis.notify();\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * Opens the width-point editor (what a double-click with the width tool does) on a width\n\t * point, or on a spot of a stroke (`t` without `index`: applying it adds a point there).\n\t * Returns false when the path has no visible stroke.\n\t */\n\topenWidthPointEdit(target: { nodeId: string; subpath?: number; index?: number | null; t?: number }): boolean {\n\t\tconst edit = this.widthEditFor(target.nodeId, target.subpath ?? 0, target.index ?? null, target.t ?? null);\n\t\tif (!edit) return false;\n\t\tthis.widthEdit = edit;\n\t\tthis.widthPoints = edit.index === null ? [] : [{ nodeId: edit.nodeId, index: edit.index, subpath: edit.subpath }];\n\t\tthis.notify();\n\t\treturn true;\n\t}\n\n\t/** Closes the width-point editor without changing anything. */\n\tcloseWidthPointEdit(): void {\n\t\tif (!this.widthEdit) return;\n\t\tthis.widthEdit = null;\n\t\tthis.notify();\n\t}\n\n\t/** Applies `values` to the width point being edited (see `setWidthPoint`) and closes the editor. */\n\tcommitWidthPointEdit(values: { t?: number; left?: number; right?: number }): VectorActionResult {\n\t\tconst edit = this.widthEdit;\n\t\tif (!edit) return { ok: false, message: 'No width point is being edited.' };\n\t\tconst r = this.setWidthPoint({ nodeId: edit.nodeId, index: edit.index }, edit.index === null ? { t: edit.t, ...values } : values);\n\t\tif (r.ok) this.closeWidthPointEdit();\n\t\treturn r;\n\t}\n\n\t/** The editor's view of point `index` (or of the stroke at `t` when index is null) of `nodeId`. */\n\tprivate widthEditFor(nodeId: string, subpath: number, index: number | null, t: number | null): VectorWidthPointEdit | null {\n\t\tconst node = this.getNode(nodeId);\n\t\tif (node?.type !== 'path') return null;\n\t\tconst g = vectorStrokeWidthGeometry(node);\n\t\tif (!g) return null;\n\t\tconst profile = sanitizeWidthProfile(node.strokeWidthProfile);\n\t\tconst p = index === null ? null : profile[index];\n\t\tif (index !== null && !p) return null;\n\t\tconst tt = p ? p.t : Math.min(Math.max(t ?? 0, 0), 1);\n\t\tconst sides = p ?? widthProfileAt(profile.length ? profile : null, tt);\n\t\tconst sp = Math.min(Math.max(0, subpath), Math.max(0, node.subpaths.length - 1));\n\t\tconst at = strokePositionPoint(g.line, sp, tt);\n\t\tconst round = (v: number) => Math.round(v * 1e6) / 1e6;\n\t\tconst q = at ? applyMatrix(this.worldOf(nodeId), { x: at.point[0], y: at.point[1] }) : { x: 0, y: 0 };\n\t\tconst point = { x: round(q.x), y: round(q.y) };\n\t\treturn { nodeId, subpath: sp, index, t: round(tt), left: round(sides.left * g.unit), right: round(sides.right * g.unit), point };\n\t}\n\n\t/**\n\t * Sets the live corner radius (and optionally the kind) of anchors: `refs`, else the selected\n\t * anchors (direct tool), else every anchor of the `ids` / selected paths. Radius 0 makes the\n\t * corners sharp again. Corners shrink to fit when drawn, so any radius is accepted.\n\t */\n\tsetCornerRadius(radius: number, options: { kind?: VectorCornerKind; refs?: readonly VectorAnchorRef[]; ids?: readonly string[] } = {}): VectorActionResult {\n\t\tif (!Number.isFinite(radius) || radius < 0) return { ok: false, message: 'The radius must be a number of at least 0.' };\n\t\tconst refs = this.cornerTargets(options);\n\t\tif (!refs.length) return { ok: false, message: 'Nothing is selected.' };\n\t\tthis.change(radius > 0 ? 'Corner Radius' : 'Sharpen Corners', () => {\n\t\t\tthis.doc = this.updateAnchors(this.doc, refs, (a) => setAnchorCorner(a, radius, options.kind));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Sets the live corner kind of the same anchors as `setCornerRadius`, keeping their radii. */\n\tsetCornerKind(kind: VectorCornerKind, options: { refs?: readonly VectorAnchorRef[]; ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst refs = this.cornerTargets(options);\n\t\tif (!refs.length) return { ok: false, message: 'Nothing is selected.' };\n\t\tthis.change('Corner Kind', () => {\n\t\t\tthis.doc = this.updateAnchors(this.doc, refs, (a) => setAnchorCorner(a, a.corner?.radius ?? 0, kind));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Anchors a corner change applies to (see `setCornerRadius`). */\n\tprivate cornerTargets(options: { refs?: readonly VectorAnchorRef[]; ids?: readonly string[] }): VectorAnchorRef[] {\n\t\tif (options.refs) return options.refs.filter((r) => getAnchor(this.doc, r));\n\t\tif (!options.ids && this.tool === 'direct' && this.anchors.length) return this.anchors.slice();\n\t\tconst out: VectorAnchorRef[] = [];\n\t\tfor (const id of options.ids ?? this.selection) {\n\t\t\tconst node = this.getNode(id);\n\t\t\tif (!node) continue;\n\t\t\tfor (const p of collectPathNodes(node)) {\n\t\t\t\tif (p.locked) continue;\n\t\t\t\tp.subpaths.forEach((sp, s) => sp.anchors.forEach((_, i) => out.push({ nodeId: p.id, subpath: s, index: i })));\n\t\t\t}\n\t\t}\n\t\treturn out;\n\t}\n\n\t/**\n\t * Outline Stroke: replaces each selected (or given) path's stroke with a filled path of the\n\t * area it paints (alignment, joins, caps, dashes and arrowheads included), placed above a copy\n\t * of the path that keeps the fill (left out when there is none). Returns the new paths' ids.\n\t */\n\toutlineStroke(options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst roots = options.ids ?? this.selectedRoots();\n\t\tconst paths: VectorPathNode[] = [];\n\t\tfor (const id of roots) {\n\t\t\tconst node = this.getNode(id);\n\t\t\tif (node) for (const p of collectPathNodes(node)) if (!p.locked && hasPaint(p.stroke) && (p.strokeWidth ?? 1) > 0) paths.push(p);\n\t\t}\n\t\tif (!paths.length) return [];\n\t\tconst created: string[] = [];\n\t\tconst replace = new Map<string, VectorNode[]>();\n\t\tfor (const p of paths) {\n\t\t\t// In the node's own space: the new paths keep its transform.\n\t\t\t// As drawn: path effects applied (the outline itself carries none).\n\t\t\tconst outline = bezierToSubpaths(vectorStrokeOutlinePath(vectorAppearanceOutline(p), p));\n\t\t\tif (!outline.length) continue;\n\t\t\tconst {\n\t\t\t\tstroke,\n\t\t\t\tstrokeWidth: _w,\n\t\t\t\tstrokeLinecap: _c,\n\t\t\t\tstrokeLinejoin: _j,\n\t\t\t\tstrokeDasharray: _d,\n\t\t\t\tstrokeAlign: _a,\n\t\t\t\tstartArrow: _s,\n\t\t\t\tendArrow: _e,\n\t\t\t\tarrowAlign: _al,\n\t\t\t\tstartArrowScale: _ss,\n\t\t\t\tendArrowScale: _es,\n\t\t\t\tstrokeWidthProfile: _wp,\n\t\t\t\teffects,\n\t\t\t\t...rest\n\t\t\t} = cloneVectorNode(p);\n\t\t\tconst id = this.newId();\n\t\t\tconst strokeNode: VectorPathNode = { ...rest, id, name: `${p.name ?? 'Path'} Stroke`, subpaths: outline, fill: stroke ?? null, stroke: null, fillRule: 'nonzero' };\n\t\t\tconst list: VectorNode[] = hasPaint(p.fill) ? [{ ...rest, ...(effects ? { effects } : {}), stroke: null }, strokeNode] : [strokeNode];\n\t\t\tcreated.push(id);\n\t\t\treplace.set(p.id, list);\n\t\t}\n\t\tif (!created.length) return [];\n\t\tthis.change('Outline Stroke', () => {\n\t\t\tlet nodes = this.doc.nodes;\n\t\t\tfor (const [id, list] of replace) nodes = spliceTree(nodes, id, list);\n\t\t\tthis.setDoc(nodes);\n\t\t\tthis.selection = created;\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn created;\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Path effects\n\t// -------------------------------------------------------------------------\n\n\t/** Selected (or given) nodes that can carry effects: the roots of the selection. */\n\tprivate effectTargets(ids?: readonly string[]): VectorNode[] {\n\t\treturn (ids ?? this.selectedRoots()).map((id) => this.getNode(id)).filter((n): n is VectorNode => !!n && !n.blendStep);\n\t}\n\n\t/**\n\t * Adds an effect of `type` to the selected (or given) nodes, at the bottom of each stack (or\n\t * at `index`), with default parameters merged with `params`. A repeater's copies start one\n\t * shape width apart. One undo step; returns the new effect ids (one per node, in order).\n\t */\n\taddEffect(type: VectorEffectType, options: { ids?: readonly string[]; params?: Record<string, unknown>; index?: number } = {}): string[] {\n\t\tconst targets = this.effectTargets(options.ids);\n\t\tif (!targets.length || !createVectorEffect(type, '')) return [];\n\t\tconst created: string[] = [];\n\t\tconst next = new Map<string, VectorNode>();\n\t\tfor (const node of targets) {\n\t\t\tconst list = node.effects ?? [];\n\t\t\tlet k = list.length + 1;\n\t\t\twhile (list.some((e) => e.id === `fx${k}`)) k++;\n\t\t\tconst params: Record<string, unknown> = { ...(options.params ?? {}) };\n\t\t\tif (type === 'repeater' && params.position === undefined) {\n\t\t\t\tconst b = bezierPathBounds(vectorAppearanceOutline(node));\n\t\t\t\tif (b && b.maxX - b.minX > 0) params.position = { x: Math.round((b.maxX - b.minX) * 1.25 * 100) / 100, y: 0 };\n\t\t\t}\n\t\t\tconst effect = createVectorEffect(type, `fx${k}`, params)!;\n\t\t\tconst at = options.index === undefined ? list.length : Math.min(list.length, Math.max(0, Math.round(options.index)));\n\t\t\tnext.set(node.id, { ...node, effects: [...list.slice(0, at), effect, ...list.slice(at)] } as VectorNode);\n\t\t\tcreated.push(effect.id);\n\t\t}\n\t\tthis.change(`Add ${VECTOR_EFFECT_LABELS[type]}`, () => this.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n)));\n\t\treturn created;\n\t}\n\n\tprivate editEffects(nodeId: string, label: string, fn: (effects: VectorEffect[]) => VectorEffect[] | string, merge?: string): VectorActionResult {\n\t\tconst node = this.getNode(nodeId);\n\t\tif (!node) return { ok: false, message: `There is no node \"${nodeId}\".` };\n\t\tconst result = fn(node.effects ?? []);\n\t\tif (typeof result === 'string') return { ok: false, message: result };\n\t\tthis.change(label, () => this.replaceNode(nodeId, (result.length ? { ...node, effects: result } : (({ effects: _e, ...rest }) => rest)(node)) as VectorNode), merge);\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * Turns an effect on or off and changes its parameters (unknown or invalid values are\n\t * ignored). With `merge` (true, or a key of your own), consecutive updates of the same effect\n\t * fold into one undo step, as a slider drag should; call `endMergedEdit` when it ends.\n\t */\n\tupdateEffect(nodeId: string, effectId: string, patch: { enabled?: boolean; params?: Record<string, unknown> }, options: { merge?: boolean | string } = {}): VectorActionResult {\n\t\tconst merge = options.merge === true ? `effect:${nodeId}:${effectId}` : options.merge || undefined;\n\t\tconst node = this.getNode(nodeId);\n\t\tconst current = node?.effects?.find((e) => e.id === effectId);\n\t\tconst label = current ? VECTOR_EFFECT_LABELS[current.type] ?? 'Effect' : 'Effect';\n\t\treturn this.editEffects(\n\t\t\tnodeId,\n\t\t\t`Edit ${label}`,\n\t\t\t(list) => {\n\t\t\t\tconst i = list.findIndex((e) => e.id === effectId);\n\t\t\t\tif (i < 0) return `Node \"${nodeId}\" has no effect \"${effectId}\".`;\n\t\t\t\tconst e = list[i];\n\t\t\t\tconst params = patch.params ? normalizeVectorEffectParams(e.type, patch.params, e.params as never) : e.params;\n\t\t\t\tconst next = { ...e, params, ...(patch.enabled !== undefined ? { enabled: patch.enabled } : {}) } as VectorEffect;\n\t\t\t\tif (next.enabled !== false) delete (next as { enabled?: boolean }).enabled;\n\t\t\t\treturn list.map((x, j) => (j === i ? next : x));\n\t\t\t},\n\t\t\tmerge\n\t\t);\n\t}\n\n\t/** Removes an effect from a node's stack. */\n\tremoveEffect(nodeId: string, effectId: string): VectorActionResult {\n\t\treturn this.editEffects(nodeId, 'Remove Effect', (list) => (list.some((e) => e.id === effectId) ? list.filter((e) => e.id !== effectId) : `Node \"${nodeId}\" has no effect \"${effectId}\".`));\n\t}\n\n\t/** Moves an effect to position `index` of its stack (0 is applied first). */\n\tmoveEffect(nodeId: string, effectId: string, index: number): VectorActionResult {\n\t\treturn this.editEffects(nodeId, 'Reorder Effects', (list) => {\n\t\t\tconst i = list.findIndex((e) => e.id === effectId);\n\t\t\tif (i < 0) return `Node \"${nodeId}\" has no effect \"${effectId}\".`;\n\t\t\tconst to = Math.min(list.length - 1, Math.max(0, Math.round(index)));\n\t\t\tconst out = list.slice();\n\t\t\tconst [e] = out.splice(i, 1);\n\t\t\tout.splice(to, 0, e);\n\t\t\treturn out;\n\t\t});\n\t}\n\n\t/**\n\t * Expand Appearance: bakes the path effects and live booleans of the selected (or given)\n\t * nodes, and of everything inside them, into plain paths. A node keeps its id (a group when\n\t * it draws as several shapes); the shapes it made get new ids. Returns the ids of the expanded\n\t * nodes (empty when none was live).\n\t */\n\texpandAppearance(options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst roots = (options.ids ?? this.selectedRoots()).map((id) => this.getNode(id)).filter((n): n is VectorNode => !!n && hasVectorAppearance(n));\n\t\tif (!roots.length) return [];\n\t\tconst fresh = (n: VectorNode): VectorNode => {\n\t\t\tconst id = n.id.includes('~fx') ? this.newId() : n.id;\n\t\t\treturn n.type === 'group' ? { ...n, id, children: n.children.map(fresh) } : { ...n, id };\n\t\t};\n\t\tconst bake = (n: VectorNode): VectorNode => {\n\t\t\tif (!hasVectorAppearance(n)) return n;\n\t\t\tif (!hasOwnVectorAppearance(n)) return { ...(n as VectorGroupNode), children: (n as VectorGroupNode).children.map(bake) };\n\t\t\tlet e = expandVectorAppearance(n);\n\t\t\t// A live group that draws as one shape becomes that path, in the group's place.\n\t\t\tif (e.type === 'group' && e.children.length === 1 && e.children[0].type === 'path') {\n\t\t\t\tconst { id: _id, opacity, name, ...child } = e.children[0];\n\t\t\t\tconst k = (e.opacity ?? 1) * (opacity ?? 1);\n\t\t\t\te = { ...child, id: e.id, name: e.name ?? name, ...placementOf(e), ...(k < 1 ? { opacity: k } : {}) } as VectorPathNode;\n\t\t\t}\n\t\t\treturn fresh(e);\n\t\t};\n\t\tconst next = new Map(roots.map((n) => [n.id, bake(n)]));\n\t\tthis.change('Expand Appearance', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n));\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn roots.map((n) => n.id);\n\t}\n\n\trename(id: string, name: string): VectorActionResult {\n\t\treturn this.updateNode(id, { name }, { label: 'Rename' });\n\t}\n\n\tsetVisible(id: string, visible: boolean): VectorActionResult {\n\t\treturn this.updateNode(id, { visible }, { label: visible ? 'Show' : 'Hide' });\n\t}\n\n\tsetLocked(id: string, locked: boolean): VectorActionResult {\n\t\tconst result = this.updateNode(id, { locked }, { label: locked ? 'Lock' : 'Unlock' });\n\t\tif (locked && result.ok && this.selection.includes(id)) {\n\t\t\tthis.selection = this.selection.filter((s) => s !== id);\n\t\t\tthis.notify();\n\t\t}\n\t\treturn result;\n\t}\n\n\t/** Removes nodes by id. */\n\tremoveNodes(ids: readonly string[], options: { label?: string } = {}): VectorActionResult {\n\t\tconst drop = new Set(ids.filter((id) => this.getNode(id)));\n\t\tif (!drop.size) return { ok: false, message: 'Nothing to delete.' };\n\t\tthis.change(options.label ?? 'Delete', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (drop.has(n.id) ? null : n)));\n\t\t\tthis.selection = this.selection.filter((id) => !drop.has(id) && this.getNode(id));\n\t\t\tthis.anchors = this.anchors.filter((r) => getAnchor(this.doc, r));\n\t\t\tif (this.editingId && !this.getNode(this.editingId)) this.editingId = null;\n\t\t\tif (this.pen && !this.getNode(this.pen.nodeId)) this.pen = null;\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Selection\n\t// -------------------------------------------------------------------------\n\n\tselect(ids: readonly string[], options: { add?: boolean; toggle?: boolean } = {}): void {\n\t\tconst valid = ids.filter((id) => this.getNode(id));\n\t\tlet next: string[];\n\t\tif (options.toggle) {\n\t\t\tconst set = new Set(this.selection);\n\t\t\tfor (const id of valid) set.has(id) ? set.delete(id) : set.add(id);\n\t\t\tnext = [...set];\n\t\t} else next = options.add ? [...new Set([...this.selection, ...valid])] : valid;\n\t\tthis.selection = next;\n\t\tif (!options.add && !options.toggle) this.anchors = this.anchors.filter((r) => next.includes(r.nodeId));\n\t\tthis.notify();\n\t}\n\n\tselectAll(): void {\n\t\tif (this.tool === 'direct' && this.editingId) {\n\t\t\tthis.anchors = getVisibleAnchors(this.view());\n\t\t} else {\n\t\t\tthis.selection = this.doc.nodes.filter((n) => !n.locked && n.visible !== false).map((n) => n.id);\n\t\t}\n\t\tthis.notify();\n\t}\n\n\tclearSelection(): void {\n\t\tif (!this.selection.length && !this.anchors.length && !this.editingId) return;\n\t\tthis.selection = [];\n\t\tthis.anchors = [];\n\t\tthis.editingId = null;\n\t\tthis.notify();\n\t}\n\n\tselectAnchors(refs: readonly VectorAnchorRef[], options: { add?: boolean } = {}): void {\n\t\tconst valid = refs.filter((r) => getAnchor(this.doc, r));\n\t\tthis.anchors = options.add ? [...this.anchors, ...valid.filter((r) => !this.anchors.some((a) => sameRef(a, r)))] : valid;\n\t\tif (valid.length && !this.editingId) this.editingId = valid[0].nodeId;\n\t\tthis.notify();\n\t}\n\n\t/** Top-level ids of the selection (members of selected groups are covered by the group). */\n\tprivate selectedRoots(): string[] {\n\t\treturn this.selection.filter((id) => {\n\t\t\tlet covered = false;\n\t\t\tconst loc = locateVectorNode(this.doc.nodes, id);\n\t\t\tlet parent = loc?.parent ?? null;\n\t\t\twhile (parent) {\n\t\t\t\tif (this.selection.includes(parent.id)) covered = true;\n\t\t\t\tparent = locateVectorNode(this.doc.nodes, parent.id)?.parent ?? null;\n\t\t\t}\n\t\t\treturn !covered;\n\t\t});\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Transforms\n\t// -------------------------------------------------------------------------\n\n\t/** `m`, given in a frame rotated by `angle`, as a document-space matrix. */\n\tprivate inFrame(angle: number, m: AffineMatrix): AffineMatrix {\n\t\tif (!angle) return m;\n\t\treturn multiplyMatrices(multiplyMatrices(rotateMatrix(-angle), m), rotateMatrix(angle));\n\t}\n\n\t/** Applies a matrix to the selected (or given) nodes as one undo step. */\n\ttransform(matrix: AffineMatrix, options: { ids?: readonly string[]; label?: string } = {}): VectorActionResult {\n\t\tconst ids = new Set(options.ids ?? this.selectedRoots());\n\t\tif (!ids.size) return { ok: false, message: 'Nothing is selected.' };\n\t\tthis.change(options.label ?? 'Transform', () => {\n\t\t\tthis.doc = this.transformed(this.doc, ids, matrix);\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** `doc` with nodes `ids` moved by `m`, given in document space. */\n\tprivate transformed(doc: VectorDocument, ids: ReadonlySet<string>, m: AffineMatrix): VectorDocument {\n\t\tconst visit = (list: readonly VectorNode[], parent: AffineMatrix): VectorNode[] => {\n\t\t\tlet changed = false;\n\t\t\tconst out = list.map((n) => {\n\t\t\t\tlet next = n;\n\t\t\t\tif (ids.has(n.id) && !n.locked) {\n\t\t\t\t\t// `m` in the node's parent space: parent → document, m, document → parent.\n\t\t\t\t\tconst local = isRoot(parent) ? m : multiplyMatrices(multiplyMatrices(parent, m), invertMatrix(parent));\n\t\t\t\t\tnext = transformVectorNode(n, local);\n\t\t\t\t} else if (n.type === 'group') {\n\t\t\t\t\tconst children = visit(n.children, multiplyMatrices(nodeMatrix(n), parent));\n\t\t\t\t\tif (children !== n.children) next = { ...n, children };\n\t\t\t\t}\n\t\t\t\tif (next !== n) changed = true;\n\t\t\t\treturn next;\n\t\t\t});\n\t\t\treturn changed ? out : (list as VectorNode[]);\n\t\t};\n\t\tconst nodes = visit(doc.nodes, IDENTITY_MATRIX);\n\t\treturn nodes === doc.nodes ? doc : { ...doc, nodes };\n\t}\n\n\ttranslate(dx: number, dy: number, options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tif (this.tool === 'direct' && this.anchors.length && !options.ids) {\n\t\t\tthis.change('Move Points', () => {\n\t\t\t\tthis.doc = this.movedAnchors(this.doc, this.anchors, dx, dy);\n\t\t\t});\n\t\t\treturn { ok: true };\n\t\t}\n\t\treturn this.transform(translateMatrix(dx, dy), { ids: options.ids, label: 'Move' });\n\t}\n\n\t/** The selection frame's centre in document space. */\n\tprivate frameCenter(frame: VectorSelectionFrame): Vec2 {\n\t\treturn applyMatrix(rotateMatrix(frame.angle), { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 });\n\t}\n\n\t/**\n\t * Scales along the selection box's own axes (rotated with the selection when its nodes\n\t * share a rotation) around `origin` (default: the box centre, in document space).\n\t */\n\tscale(sx: number, sy: number = sx, options: { ids?: readonly string[]; origin?: Vec2 } = {}): VectorActionResult {\n\t\tconst frame = this.frameOf(options.ids ?? this.selectedRoots());\n\t\tif (!frame) return { ok: false, message: 'Nothing is selected.' };\n\t\tconst origin = applyMatrix(rotateMatrix(-frame.angle), options.origin ?? this.frameCenter(frame));\n\t\treturn this.transform(this.inFrame(frame.angle, scaleMatrix(sx, sy, origin)), { ids: options.ids, label: 'Scale' });\n\t}\n\n\t/** Rotates clockwise by `degrees` around `origin` (default: the selection centre). */\n\trotate(degrees: number, options: { ids?: readonly string[]; origin?: Vec2 } = {}): VectorActionResult {\n\t\tconst frame = this.frameOf(options.ids ?? this.selectedRoots());\n\t\tif (!frame) return { ok: false, message: 'Nothing is selected.' };\n\t\tconst origin = options.origin ?? this.frameCenter(frame);\n\t\treturn this.transform(rotateMatrix(degrees, origin), { ids: options.ids, label: 'Rotate' });\n\t}\n\n\t/** Sets each selected (or given) node's rotation to `degrees`, turning it around its own centre. */\n\tsetRotation(degrees: number, options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst ids = options.ids ?? this.selectedRoots();\n\t\tif (!ids.length) return { ok: false, message: 'Nothing is selected.' };\n\t\tthis.change('Set Rotation', () => {\n\t\t\tfor (const id of ids) {\n\t\t\t\tconst frame = this.frameOf([id]);\n\t\t\t\tif (!frame) continue;\n\t\t\t\tconst delta = normalizeAngle(degrees - frame.angle);\n\t\t\t\tif (Math.abs(delta) > 1e-9) this.doc = this.transformed(this.doc, new Set([id]), rotateMatrix(delta, this.frameCenter(frame)));\n\t\t\t}\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Mirrors along the selection box's own axes. */\n\tflip(axis: 'horizontal' | 'vertical', options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst frame = this.frameOf(options.ids ?? this.selectedRoots());\n\t\tif (!frame) return { ok: false, message: 'Nothing is selected.' };\n\t\tconst c = { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 };\n\t\tconst m = axis === 'horizontal' ? scaleMatrix(-1, 1, c) : scaleMatrix(1, -1, c);\n\t\treturn this.transform(this.inFrame(frame.angle, m), { ids: options.ids, label: axis === 'horizontal' ? 'Flip Horizontal' : 'Flip Vertical' });\n\t}\n\n\t/**\n\t * Aligns the selected nodes to their combined bounds, or to the artboard when one node is\n\t * selected (or `to: 'artboard'`).\n\t */\n\talign(edge: VectorAlign, options: { ids?: readonly string[]; to?: 'selection' | 'artboard' } = {}): VectorActionResult {\n\t\tconst ids = options.ids ?? this.selectedRoots();\n\t\tif (!ids.length) return { ok: false, message: 'Nothing is selected.' };\n\t\tconst target = options.to === 'artboard' || ids.length === 1 ? { x: 0, y: 0, width: this.doc.width, height: this.doc.height } : this.boundsOf(ids)!;\n\t\tthis.change(`Align ${edge[0].toUpperCase()}${edge.slice(1)}`, () => {\n\t\t\tfor (const id of ids) {\n\t\t\t\tconst b = this.boundsOf([id]);\n\t\t\t\tif (!b) continue;\n\t\t\t\tlet dx = 0;\n\t\t\t\tlet dy = 0;\n\t\t\t\tif (edge === 'left') dx = target.x - b.x;\n\t\t\t\tif (edge === 'center') dx = target.x + target.width / 2 - (b.x + b.width / 2);\n\t\t\t\tif (edge === 'right') dx = target.x + target.width - (b.x + b.width);\n\t\t\t\tif (edge === 'top') dy = target.y - b.y;\n\t\t\t\tif (edge === 'middle') dy = target.y + target.height / 2 - (b.y + b.height / 2);\n\t\t\t\tif (edge === 'bottom') dy = target.y + target.height - (b.y + b.height);\n\t\t\t\tif (dx || dy) this.doc = this.transformed(this.doc, new Set([id]), translateMatrix(dx, dy));\n\t\t\t}\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Spaces three or more selected nodes evenly (equal gaps). */\n\tdistribute(direction: VectorDistribute, options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst ids = options.ids ?? this.selectedRoots();\n\t\tconst items = ids.map((id) => ({ id, b: this.boundsOf([id]) })).filter((i): i is { id: string; b: VectorBounds } => !!i.b);\n\t\tif (items.length < 3) return { ok: false, message: 'Select at least three objects to distribute.' };\n\t\tconst horizontal = direction === 'horizontal';\n\t\tconst pos = (b: VectorBounds) => (horizontal ? b.x : b.y);\n\t\tconst size = (b: VectorBounds) => (horizontal ? b.width : b.height);\n\t\titems.sort((a, b) => pos(a.b) - pos(b.b));\n\t\tconst first = items[0].b;\n\t\tconst last = items[items.length - 1].b;\n\t\tconst total = pos(last) + size(last) - pos(first);\n\t\tconst gap = (total - items.reduce((s, i) => s + size(i.b), 0)) / (items.length - 1);\n\t\tthis.change(horizontal ? 'Distribute Horizontally' : 'Distribute Vertically', () => {\n\t\t\tlet cursor = pos(first);\n\t\t\tfor (const item of items) {\n\t\t\t\tconst d = cursor - pos(item.b);\n\t\t\t\tif (Math.abs(d) > 1e-9) this.doc = this.transformed(this.doc, new Set([item.id]), horizontal ? translateMatrix(d, 0) : translateMatrix(0, d));\n\t\t\t\tcursor += size(item.b) + gap;\n\t\t\t}\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Structure\n\t// -------------------------------------------------------------------------\n\n\t/** Copies the selection (offset by `offset`, default 10, 10) on top of each original. */\n\tduplicate(options: { ids?: readonly string[]; offset?: Vec2 } = {}): string[] {\n\t\tconst ids = options.ids ?? this.selectedRoots();\n\t\tconst offset = options.offset ?? { x: 10, y: 10 };\n\t\tconst created: string[] = [];\n\t\tif (!ids.length) return created;\n\t\tthis.change('Duplicate', () => {\n\t\t\tthis.doc = this.withDuplicates(this.doc, ids, offset, created);\n\t\t\tthis.selection = created;\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn created;\n\t}\n\n\tprivate withDuplicates(doc: VectorDocument, ids: readonly string[], offset: Vec2, created: string[]): VectorDocument {\n\t\tconst set = new Set(ids);\n\t\tconst m = translateMatrix(offset.x, offset.y);\n\t\tconst insert = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\tconst out: VectorNode[] = [];\n\t\t\tfor (const node of list) {\n\t\t\t\tconst n = node.type === 'group' ? { ...node, children: insert(node.children) } : node;\n\t\t\t\tout.push(n);\n\t\t\t\tif (set.has(node.id)) {\n\t\t\t\t\tconst copy = transformVectorNode(this.freshIds(node), m);\n\t\t\t\t\tcreated.push(copy.id);\n\t\t\t\t\tout.push(copy);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out;\n\t\t};\n\t\treturn { ...doc, nodes: insert(doc.nodes) };\n\t}\n\n\t/** Groups the selected nodes (they must share a parent). Returns the group id. */\n\tgroup(options: { ids?: readonly string[]; name?: string } = {}): string | null {\n\t\tconst ids = options.ids ?? this.selectedRoots();\n\t\tif (!ids.length) return null;\n\t\tconst locs = ids.map((id) => locateVectorNode(this.doc.nodes, id));\n\t\tif (locs.some((l) => !l)) return null;\n\t\tconst parent = locs[0]!.parent?.id ?? null;\n\t\tif (locs.some((l) => (l!.parent?.id ?? null) !== parent)) return null;\n\t\tconst id = this.newId();\n\t\tthis.change('Group', () => {\n\t\t\tconst set = new Set(ids);\n\t\t\tconst regroup = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\t\tconst members = list.filter((n) => set.has(n.id));\n\t\t\t\tconst top = Math.max(...members.map((m) => list.indexOf(m)));\n\t\t\t\tconst out: VectorNode[] = [];\n\t\t\t\tlist.forEach((n, i) => {\n\t\t\t\t\tif (i === top) out.push({ id, type: 'group', name: options.name ?? 'Group', children: members });\n\t\t\t\t\tif (!set.has(n.id)) out.push(n);\n\t\t\t\t});\n\t\t\t\treturn out;\n\t\t\t};\n\t\t\tif (parent === null) this.setDoc(regroup(this.doc.nodes));\n\t\t\telse this.setDoc(mapTree(this.doc.nodes, (n) => (n.id === parent && n.type === 'group' ? { ...n, children: regroup(n.children) } : n)));\n\t\t\tthis.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** Replaces selected groups with their children. */\n\tungroup(options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst groups = (options.ids ?? this.selection).map((id) => this.getNode(id)).filter((n): n is VectorGroupNode => n?.type === 'group');\n\t\tif (!groups.length) return { ok: false, message: 'Select a group to ungroup.' };\n\t\tconst set = new Set(groups.map((g) => g.id));\n\t\tthis.change('Ungroup', () => {\n\t\t\tconst flatten = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\t\tconst out: VectorNode[] = [];\n\t\t\t\tlet changed = false;\n\t\t\t\tfor (const n of list) {\n\t\t\t\t\tif (set.has(n.id) && n.type === 'group') {\n\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t\tconst opacity = n.opacity;\n\t\t\t\t\t\tconst m = nodeMatrix(n);\n\t\t\t\t\t\tout.push(\n\t\t\t\t\t\t\t...n.children\n\t\t\t\t\t\t\t\t.map((c) => (c.blendStep ? (({ blendStep: _s, locked: _l, ...rest }) => rest as VectorNode)(c) : c))\n\t\t\t\t\t\t\t\t.map((c) => (c.booleanOperation ? (({ booleanOperation: _b, ...rest }) => rest as VectorNode)(c) : c))\n\t\t\t\t\t\t\t\t.map((c) => (n.transform ? transformVectorNode(c, m) : c))\n\t\t\t\t\t\t\t\t.map((c) => (opacity !== undefined && opacity < 1 ? { ...c, opacity: (c.opacity ?? 1) * opacity } : c))\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (n.type === 'group') {\n\t\t\t\t\t\tconst children = flatten(n.children);\n\t\t\t\t\t\tif (children !== n.children) changed = true;\n\t\t\t\t\t\tout.push(children !== n.children ? { ...n, children } : n);\n\t\t\t\t\t} else out.push(n);\n\t\t\t\t}\n\t\t\t\treturn changed ? out : (list as VectorNode[]);\n\t\t\t};\n\t\t\tthis.setDoc(flatten(this.doc.nodes));\n\t\t\tthis.selection = groups.flatMap((g) => g.children.map((c) => c.id));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Changes stacking order within each node's parent. */\n\tarrange(op: VectorArrange, options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst set = new Set(options.ids ?? this.selectedRoots());\n\t\tif (!set.size) return { ok: false, message: 'Nothing is selected.' };\n\t\tconst reorder = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\tconst out = list.map((n) => (n.type === 'group' ? { ...n, children: reorder(n.children) } : n));\n\t\t\tif (op === 'front') return [...out.filter((n) => !set.has(n.id)), ...out.filter((n) => set.has(n.id))];\n\t\t\tif (op === 'back') return [...out.filter((n) => set.has(n.id)), ...out.filter((n) => !set.has(n.id))];\n\t\t\tif (op === 'forward') {\n\t\t\t\tfor (let i = out.length - 2; i >= 0; i--) if (set.has(out[i].id) && !set.has(out[i + 1].id)) [out[i], out[i + 1]] = [out[i + 1], out[i]];\n\t\t\t} else {\n\t\t\t\tfor (let i = 1; i < out.length; i++) if (set.has(out[i].id) && !set.has(out[i - 1].id)) [out[i], out[i - 1]] = [out[i - 1], out[i]];\n\t\t\t}\n\t\t\treturn out;\n\t\t};\n\t\tconst labels = { front: 'Bring to Front', forward: 'Bring Forward', backward: 'Send Backward', back: 'Send to Back' };\n\t\tthis.change(labels[op], () => this.setDoc(reorder(this.doc.nodes)));\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * Combines the selected paths (two or more) into one with a boolean operation. `subtract`\n\t * removes the upper shapes from the lowest one. The result takes the lowest shape's style\n\t * and place. Shapes take part as drawn (path effects and live booleans applied).\n\t *\n\t * With `live`, the operands stay editable instead: a lone live boolean group gets the\n\t * operation (its operands follow it again), operands of live boolean groups get it as their\n\t * own, and anything else (two or more nodes with one parent) is wrapped in a new live boolean\n\t * group. Returns the result's (or the live group's) id, or null.\n\t */\n\tbooleanOp(op: VectorBooleanOperation, options: { ids?: readonly string[]; live?: boolean } = {}): string | null {\n\t\tconst roots = options.ids ?? this.selectedRoots();\n\t\tif (options.live) return this.liveBooleanOp(op, roots);\n\t\tconst entries = vectorPathEntries(this.doc, roots, { locked: true });\n\t\tif (entries.length < 2) return null;\n\t\tconst result = pathBoolean(\n\t\t\tBOOLEAN_OPS[op],\n\t\t\tentries.map((e) => ({\n\t\t\t\tpath: vectorPathWorldBezier({ ...e.node, subpaths: e.node.subpaths.filter((s) => s.anchors.length > 1).map((s) => ({ ...s, closed: true })) }, e.parent),\n\t\t\t\tfillRule: e.node.fillRule ?? 'nonzero'\n\t\t\t}))\n\t\t);\n\t\tconst base = entries[0].node;\n\t\tconst anchorRoot = roots.find((r) => this.holds(r, base.id)) ?? base.id;\n\t\tconst subpaths = bezierToSubpaths(this.intoParentSpace(anchorRoot, result.path));\n\t\tif (result.error || !subpaths.length) return null;\n\t\tconst id = this.newId();\n\t\tconst { transform: _t, effects: _e, booleanOperation: _b, ...baseRest } = cloneVectorNode(base);\n\t\tconst node: VectorPathNode = { ...baseRest, id, name: BOOLEAN_LABELS[op], subpaths };\n\t\tconst drop = new Set([...entries.map((e) => e.node.id), ...roots]);\n\t\tthis.change(BOOLEAN_LABELS[op], () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === anchorRoot ? node : drop.has(n.id) ? null : n)));\n\t\t\tthis.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** Whether stored node `rootId` holds `id` as drawn (`id` may be an expanded path inside it). */\n\tprivate holds(rootId: string, id: string): boolean {\n\t\tconst n = this.getNode(rootId);\n\t\treturn !!n && (!!findVectorNode([n], id) || !!findVectorNode([expandVectorAppearance(n)], id));\n\t}\n\n\tprivate liveBooleanOp(op: VectorBooleanOperation, roots: readonly string[]): string | null {\n\t\tconst nodes = roots.map((id) => this.getNode(id));\n\t\tif (!nodes.length || nodes.some((n) => !n)) return null;\n\t\tconst label = `Live ${BOOLEAN_LABELS[op]}`;\n\t\tconst lone = nodes.length === 1 ? nodes[0] : null;\n\t\tif (isVectorLiveBoolean(lone)) {\n\t\t\tconst children = lone.children.map((c) => (c.booleanOperation ? (({ booleanOperation: _b, ...rest }) => rest as VectorNode)(c) : c));\n\t\t\tthis.change(label, () => this.replaceNode(lone.id, { ...lone, liveBoolean: op, children }));\n\t\t\treturn lone.id;\n\t\t}\n\t\tconst parents = roots.map((id) => locateVectorNode(this.doc.nodes, id)?.parent ?? null);\n\t\tif (parents.every((g) => isVectorLiveBoolean(g))) {\n\t\t\tconst set = new Set(roots);\n\t\t\tthis.change(label, () =>\n\t\t\t\tthis.setDoc(\n\t\t\t\t\tmapTree(this.doc.nodes, (n) => {\n\t\t\t\t\t\tif (n.type !== 'group' || !n.liveBoolean || !n.children.some((c) => set.has(c.id))) return n;\n\t\t\t\t\t\tconst group = n;\n\t\t\t\t\t\tconst children = group.children.map((c) => {\n\t\t\t\t\t\t\tif (!set.has(c.id)) return c;\n\t\t\t\t\t\t\tconst { booleanOperation: _b, ...rest } = c;\n\t\t\t\t\t\t\treturn (op === group.liveBoolean ? rest : { ...rest, booleanOperation: op }) as VectorNode;\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn { ...group, children };\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t);\n\t\t\treturn parents[0]!.id;\n\t\t}\n\t\tif (nodes.length < 2) return null;\n\t\tconst parent = parents[0]?.id ?? null;\n\t\tif (parents.some((g) => (g?.id ?? null) !== parent)) return null;\n\t\tconst id = this.newId();\n\t\tthis.change(label, () => {\n\t\t\tconst set = new Set(roots);\n\t\t\tconst wrap = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\t\tconst members = list.filter((n) => set.has(n.id)).map((c) => (c.booleanOperation ? (({ booleanOperation: _b, ...rest }) => rest as VectorNode)(c) : c));\n\t\t\t\tconst top = Math.max(...list.map((n, i) => (set.has(n.id) ? i : -1)));\n\t\t\t\tconst out: VectorNode[] = [];\n\t\t\t\tlist.forEach((n, i) => {\n\t\t\t\t\tif (i === top) out.push({ id, type: 'group', name: BOOLEAN_LABELS[op], liveBoolean: op, children: members });\n\t\t\t\t\tif (!set.has(n.id)) out.push(n);\n\t\t\t\t});\n\t\t\t\treturn out;\n\t\t\t};\n\t\t\tif (parent === null) this.setDoc(wrap(this.doc.nodes));\n\t\t\telse this.setDoc(mapTree(this.doc.nodes, (n) => (n.id === parent && n.type === 'group' ? { ...n, children: wrap(n.children) } : n)));\n\t\t\tthis.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** Joins the selected paths into one compound path (subpaths kept, lowest style). */\n\tcombine(options: { ids?: readonly string[] } = {}): string | null {\n\t\tconst roots = options.ids ?? this.selectedRoots();\n\t\tconst entries = vectorPathEntries(this.doc, roots, { locked: true });\n\t\tif (entries.length < 2) return null;\n\t\tconst paths = entries.map((e) => e.node);\n\t\tconst id = this.newId();\n\t\tconst first = roots.find((r) => this.holds(r, paths[0].id)) ?? paths[0].id;\n\t\tconst subpaths = entries.flatMap((e) => bezierToSubpaths(this.intoParentSpace(first, vectorPathWorldBezier(e.node, e.parent))));\n\t\tconst { transform: _t, effects: _e, booleanOperation: _b, ...baseRest } = cloneVectorNode(paths[0]);\n\t\tconst node: VectorPathNode = { ...baseRest, id, name: 'Compound Path', subpaths };\n\t\tconst drop = new Set([...paths.map((p) => p.id), ...roots]);\n\t\tthis.change('Combine', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === first ? node : drop.has(n.id) ? null : n)));\n\t\t\tthis.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** A document-space path mapped into the parent space of node `id`. */\n\tprivate intoParentSpace(id: string, path: BezierPath): BezierPath {\n\t\tconst parent = vectorParentMatrix(this.doc.nodes, id) ?? IDENTITY_MATRIX;\n\t\treturn isRoot(parent) ? path : transformBezierPath(path, invertMatrix(parent));\n\t}\n\n\t/** Splits selected compound paths into one path per subpath. */\n\tbreakApart(options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst paths = (options.ids ?? this.selection).map((id) => this.getNode(id)).filter((n): n is VectorPathNode => n?.type === 'path' && n.subpaths.length > 1);\n\t\tif (!paths.length) return { ok: false, message: 'Select a compound path to break apart.' };\n\t\tconst created: string[] = [];\n\t\tconst set = new Set(paths.map((p) => p.id));\n\t\tthis.change('Break Apart', () => {\n\t\t\tconst split = (list: readonly VectorNode[]): VectorNode[] =>\n\t\t\t\tlist.flatMap((n): VectorNode[] => {\n\t\t\t\t\tif (n.type === 'group') return [{ ...n, children: split(n.children) }];\n\t\t\t\t\tif (!set.has(n.id)) return [n];\n\t\t\t\t\treturn n.subpaths.map((sp, i) => {\n\t\t\t\t\t\tconst id = i === 0 ? n.id : this.newId();\n\t\t\t\t\t\tcreated.push(id);\n\t\t\t\t\t\treturn { ...n, id, subpaths: [sp] };\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\tthis.setDoc(split(this.doc.nodes));\n\t\t\tthis.selection = created;\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Pathfinder, Shape Builder and paint bucket\n\t// -------------------------------------------------------------------------\n\n\t/**\n\t * Pathfinder: Divide, Trim, Merge, Crop, Outline or Minus Back over the selected (or given)\n\t * paths, in document space (rotated nodes and groups included, live corners cut). Unfilled\n\t * stroked paths take part as the area their stroke paints (Divide: open ones cut instead).\n\t * The results replace the operands at the place of the top-most one, grouped when there are\n\t * several; each keeps the style of the operand whose area it is. Returns the new path ids,\n\t * or an empty list when nothing came out.\n\t */\n\tpathfinder(op: VectorPathfinderOperation, options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst roots = options.ids ?? this.selectedRoots();\n\t\tconst entries = vectorPathEntries(this.doc, roots);\n\t\tif (entries.length < (op === 'divide' || op === 'outline' ? 1 : 2)) return [];\n\t\tconst shapes = entries.map((e) => vectorPathfinderShape(e, op));\n\t\tconst result = runPathfinder(op, shapes);\n\t\tif (result.error || !result.pieces.length) return [];\n\t\tconst last = entries[entries.length - 1].node.id;\n\t\tconst topRoot = [...roots].reverse().find((r) => this.holds(r, last)) ?? last;\n\t\tconst nodes: VectorPathNode[] = [];\n\t\tfor (const piece of result.pieces) {\n\t\t\tconst subpaths = bezierToSubpaths(this.intoParentSpace(topRoot, piece.path)).map((sp) => (piece.open ? { ...sp, closed: false } : sp));\n\t\t\tif (!subpaths.length) continue;\n\t\t\tconst source = entries[piece.source].node;\n\t\t\tconst style = vectorResultStyle(source, shapes[piece.source].paint, op === 'outline');\n\t\t\tnodes.push({ ...style, type: 'path', id: this.newId(), name: source.name ?? 'Path', subpaths });\n\t\t}\n\t\tif (!nodes.length) return [];\n\t\tconst label = VECTOR_PATHFINDER_LABELS[op];\n\t\tconst container: VectorNode = nodes.length > 1 ? { id: this.newId(), type: 'group', name: label, children: nodes } : nodes[0];\n\t\tconst drop = new Set([...entries.map((e) => e.node.id), ...roots]);\n\t\tthis.change(label, () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === topRoot ? container : drop.has(n.id) ? null : n)));\n\t\t\tthis.selection = [container.id];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn nodes.map((n) => n.id);\n\t}\n\n\t/** The Shape Builder map of the selected (or given) paths, cached per document and selection. */\n\t// -------------------------------------------------------------------------\n\t// Blends\n\t// -------------------------------------------------------------------------\n\n\t/** Selected (or given) blend groups; a selected key or step counts as its blend. */\n\tprivate blendTargets(ids?: readonly string[]): (VectorGroupNode & { blend: VectorBlend })[] {\n\t\tconst out = new Map<string, VectorGroupNode & { blend: VectorBlend }>();\n\t\tfor (const id of ids ?? this.selection) {\n\t\t\tconst node = this.getNode(id);\n\t\t\tconst group = isVectorBlendGroup(node) ? node : locateVectorNode(this.doc.nodes, id)?.parent;\n\t\t\tif (isVectorBlendGroup(group)) out.set(group.id, group);\n\t\t}\n\t\treturn [...out.values()];\n\t}\n\n\t/**\n\t * Blends two or more selected (or given) paths or groups: they become the keys of a live\n\t * blend group (bottom to top, or in the order given with `keepOrder`) whose in-betweens are\n\t * regenerated whenever a key or the options change. The keys must share a parent. Returns\n\t * the blend group's id, or null.\n\t */\n\tblend(input: VectorBlendInput = {}): string | null {\n\t\tconst ids = [...new Set(input.ids ?? this.selectedRoots())].filter((id) => {\n\t\t\tconst n = this.getNode(id);\n\t\t\treturn !!n && !n.blendStep && !n.locked;\n\t\t});\n\t\tif (ids.length < 2) return null;\n\t\tconst locs = ids.map((id) => locateVectorNode(this.doc.nodes, id)!);\n\t\tconst parent = locs[0].parent?.id ?? null;\n\t\tif (locs.some((l) => (l.parent?.id ?? null) !== parent)) return null;\n\t\tconst order = input.keepOrder ? ids : ids.slice().sort((a, b) => locs[ids.indexOf(a)].index - locs[ids.indexOf(b)].index);\n\t\tconst blend: VectorBlend = blendOptionsOf({ ...this.blendDefaults, ...input });\n\t\tif (input.starts) {\n\t\t\tconst starts = Object.fromEntries(Object.entries(input.starts).filter(([k, v]) => order.includes(k) && Number.isInteger(v) && v >= 0));\n\t\t\tif (Object.keys(starts).length) blend.starts = starts;\n\t\t}\n\t\tconst id = this.newId();\n\t\tthis.change('Blend', () => {\n\t\t\tconst set = new Set(order);\n\t\t\tconst regroup = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\t\tconst members = order.map((m) => list.find((n) => n.id === m)!);\n\t\t\t\tconst top = Math.max(...members.map((m) => list.indexOf(m)));\n\t\t\t\tconst out: VectorNode[] = [];\n\t\t\t\tlist.forEach((n, i) => {\n\t\t\t\t\tif (i === top) out.push({ id, type: 'group', name: input.name ?? 'Blend', blend, children: members });\n\t\t\t\t\tif (!set.has(n.id)) out.push(n);\n\t\t\t\t});\n\t\t\t\treturn out;\n\t\t\t};\n\t\t\tif (parent === null) this.setDoc(regroup(this.doc.nodes));\n\t\t\telse this.setDoc(mapTree(this.doc.nodes, (n) => (n.id === parent && n.type === 'group' ? { ...n, children: regroup(n.children) } : n)));\n\t\t\tthis.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** Sets the options new blends get (spacing, steps, distance, orientation, morph). */\n\tsetBlendDefaults(patch: Partial<VectorBlendOptions>): void {\n\t\tthis.blendDefaults = { ...this.blendDefaults, ...blendOptionsOf(patch) };\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Adds a node as the last key of a blend (it must share the blend's parent); `start` is the\n\t * anchor of its first subpath the blend starts from. One undo step.\n\t */\n\taddBlendKey(blendId: string, nodeId: string, options: { start?: number | null } = {}): VectorActionResult {\n\t\tconst group = this.getNode(blendId);\n\t\tconst node = this.getNode(nodeId);\n\t\tif (!isVectorBlendGroup(group)) return { ok: false, message: `\"${blendId}\" is not a blend.` };\n\t\tif (!node || node.blendStep || node.locked || node.id === blendId) return { ok: false, message: `\"${nodeId}\" cannot be a key.` };\n\t\tconst parentOf = (id: string) => locateVectorNode(this.doc.nodes, id)?.parent?.id ?? null;\n\t\tif (parentOf(blendId) !== parentOf(nodeId)) return { ok: false, message: 'The new key must sit beside the blend (same parent).' };\n\t\tconst start = options.start;\n\t\tthis.change('Blend', () => {\n\t\t\tconst blend = start !== null && start !== undefined ? { ...group.blend, starts: { ...group.blend.starts, [nodeId]: start } } : group.blend;\n\t\t\t// The key leaves its place and joins the blend.\n\t\t\tconst m = invertMatrix(nodeMatrix(group));\n\t\t\tconst key = group.transform ? transformVectorNode(node, m) : node;\n\t\t\t// Two passes: the key must not be removed again from inside the blend.\n\t\t\tconst without = mapTree(this.doc.nodes, (n) => (n.id === nodeId ? null : n));\n\t\t\tthis.setDoc(spliceTree(without, blendId, [{ ...group, blend, children: [...group.children, key] }]));\n\t\t\tthis.selection = [blendId];\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Changes the spacing, steps, distance, orientation or morph of the selected (or given) blends. */\n\tsetBlendOptions(patch: Partial<VectorBlendOptions>, options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst groups = this.blendTargets(options.ids);\n\t\tif (!groups.length) return { ok: false, message: 'Select a blend.' };\n\t\tconst clean = blendOptionsOf(patch);\n\t\tif (!Object.keys(clean).length) return { ok: false, message: 'No valid blend options were given.' };\n\t\tconst set = new Set(groups.map((g) => g.id));\n\t\tthis.change('Blend Options', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (set.has(n.id) && n.type === 'group' && n.blend ? { ...n, blend: { ...n.blend, ...clean } } : n)));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Expand: turns the selected (or given) blends into plain groups of their keys and steps. Returns their ids. */\n\texpandBlend(options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst groups = this.blendTargets(options.ids);\n\t\tif (!groups.length) return [];\n\t\tconst set = new Set(groups.map((g) => g.id));\n\t\tconst used = new Set<string>();\n\t\twalkVectorNodes(this.doc.nodes, (n) => used.add(n.id));\n\t\tconst fresh = () => {\n\t\t\tlet id = this.createId();\n\t\t\twhile (used.has(id)) id = this.createId();\n\t\t\tused.add(id);\n\t\t\treturn id;\n\t\t};\n\t\tthis.change('Expand Blend', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (set.has(n.id) && n.type === 'group' ? expandVectorBlend(n, fresh) : n)));\n\t\t\tthis.selection = [...set];\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn [...set];\n\t}\n\n\t/** Release: replaces the selected (or given) blends with their keys (and a replaced spine as a path). */\n\treleaseBlend(options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst groups = this.blendTargets(options.ids);\n\t\tif (!groups.length) return [];\n\t\tconst released: string[] = [];\n\t\tthis.change('Release Blend', () => {\n\t\t\tfor (const g of groups) {\n\t\t\t\tconst nodes = releaseVectorBlend(g, () => this.newId());\n\t\t\t\treleased.push(...nodes.map((n) => n.id));\n\t\t\t\tthis.setDoc(spliceTree(this.doc.nodes, g.id, nodes));\n\t\t\t}\n\t\t\tthis.selection = released;\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn released;\n\t}\n\n\t/**\n\t * Replace Spine: the selected blend's steps follow the selected path instead of the straight\n\t * lines between its keys. The path's first subpath becomes the spine (the path is removed)\n\t * and the keys move onto it, spread evenly by arc length.\n\t */\n\treplaceBlendSpine(options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst roots = options.ids ?? this.selectedRoots();\n\t\tconst groups = this.blendTargets(roots);\n\t\tconst paths = roots.map((id) => this.getNode(id)).filter((n): n is VectorPathNode => n?.type === 'path' && !n.blendStep && !isVectorBlendGroup(locateVectorNode(this.doc.nodes, n.id)?.parent));\n\t\tif (groups.length !== 1 || paths.length !== 1) return { ok: false, message: 'Select one blend and one path to be its spine.' };\n\t\tconst group = groups[0];\n\t\tconst path = paths[0];\n\t\tconst first = path.subpaths.find((sp) => sp.anchors.length >= 2);\n\t\tif (!first) return { ok: false, message: 'The spine needs a path with at least two points.' };\n\t\t// The path's first subpath in the blend group's own space.\n\t\tconst m = multiplyMatrices(this.worldOf(path.id), invertMatrix(this.worldOf(group.id)));\n\t\tconst spine = transformVectorNode<VectorPathNode>({ id: '', type: 'path', subpaths: [first] }, m).subpaths[0];\n\t\tthis.change('Replace Spine', () => {\n\t\t\tconst withSpine: VectorGroupNode = { ...group, blend: { ...group.blend, spine } };\n\t\t\tconst targets = vectorBlendKeyTargets(withSpine);\n\t\t\tlet k = 0;\n\t\t\tconst children = withSpine.children.map((c) => {\n\t\t\t\tif (c.blendStep) return c;\n\t\t\t\tconst target = targets[k++];\n\t\t\t\tconst b = vectorNodeBounds(c);\n\t\t\t\tif (!target || !b) return c;\n\t\t\t\tconst dx = target[0] - (b.x + b.width / 2);\n\t\t\t\tconst dy = target[1] - (b.y + b.height / 2);\n\t\t\t\treturn Math.hypot(dx, dy) > 1e-9 ? transformVectorNode(c, translateMatrix(dx, dy)) : c;\n\t\t\t});\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === path.id ? null : n.id === group.id ? { ...withSpine, children } : n)));\n\t\t\tthis.selection = [group.id];\n\t\t\tthis.anchors = [];\n\t\t\tif (this.editingId === path.id) this.editingId = null;\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\tprivate shapeBuilderMap(ids?: readonly string[]): VectorShapeBuilderMap | null {\n\t\tconst roots = ids ?? this.selectedRoots();\n\t\tconst key = roots.join('\\u0000');\n\t\tif (this.builderCache?.doc === this.doc && this.builderCache.key === key) return this.builderCache.map;\n\t\t// The Shape Builder edits the shapes it takes area from: their stored geometry.\n\t\tconst entries = roots.length ? vectorPathEntries(this.doc, roots, { expand: false }) : [];\n\t\tconst map = entries.length ? vectorShapeBuilderMap(entries) : null;\n\t\tthis.builderCache = { doc: this.doc, key, map };\n\t\treturn map;\n\t}\n\n\t/** The faces the Shape Builder offers for the selected (or given) paths (document units). */\n\tgetShapeBuilderFaces(options: { ids?: readonly string[] } = {}): VectorShapeBuilderFace[] {\n\t\tconst m = this.shapeBuilderMap(options.ids);\n\t\tif (!m) return [];\n\t\treturn m.map.faces.map((f, index) => {\n\t\t\tconst p = m.map.faceInterior(index);\n\t\t\treturn {\n\t\t\t\tindex,\n\t\t\t\td: bezierPathToSvg(f.path, { precision: 3 }),\n\t\t\t\tarea: f.area,\n\t\t\t\tsources: m.map.faceSources(index).map((i) => m.entries[i].node.id),\n\t\t\t\tpoint: p ? { x: p[0], y: p[1] } : null\n\t\t\t};\n\t\t});\n\t}\n\n\t/** Pick distance of the Shape Builder's erase for line pieces (document units). */\n\tprivate builderLineTolerance(): number {\n\t\treturn Math.max(this.hitRadius, 4) / this.viewport.zoom;\n\t}\n\n\t/**\n\t * Shape Builder gesture along `points` (document units; one point is a click) over the faces\n\t * the selected (or given) paths make. Merges the faces touched into one shape styled like the\n\t * top-most shape over the first face (the current fill for a face only outlines enclose);\n\t * with `erase` deletes them, the pieces of unfilled open paths and the edges of other outlines\n\t * (cut where paths meet) within `tolerance`: a deleted edge opens a closed subpath there,\n\t * splits an open one or takes a subpath it runs all the way round, and edges bounding a face\n\t * deleted in the same gesture go with the face. Every shape covering a touched face loses\n\t * that area; paths keep their own transforms. One undo step.\n\t */\n\tshapeBuilder(points: readonly Vec2[], options: { ids?: readonly string[]; erase?: boolean; tolerance?: number } = {}): VectorShapeBuilderResult {\n\t\tconst none = (message: string): VectorShapeBuilderResult => ({ ok: false, message, merged: null, faces: 0, lines: 0, edges: 0 });\n\t\tif (!points.length || !points.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y))) return none('Give the points of the gesture.');\n\t\tconst m = this.shapeBuilderMap(options.ids);\n\t\tif (!m) return none('Select paths that make faces.');\n\t\tconst erase = !!options.erase;\n\t\tconst tol = erase ? (options.tolerance ?? this.builderLineTolerance()) : null;\n\t\tconst touched: VectorBuilderTouched = { faces: [], lines: [], edges: [] };\n\t\tconst size = m.map.faces.reduce((s, f) => Math.max(s, f.bounds.maxX - f.bounds.minX, f.bounds.maxY - f.bounds.minY), 1);\n\t\tfor (const p of samplePolyline(points, Math.max(size / 2000, 0.25 / this.viewport.zoom))) addBuilderHit(touched, vectorShapeBuilderHit(m, p, tol));\n\t\tif (!touched.faces.length && !touched.lines.length && !touched.edges.length) return none('There is no face under the pointer.');\n\t\treturn this.applyShapeBuilder(m, touched, erase);\n\t}\n\n\tprivate applyShapeBuilder(m: VectorShapeBuilderMap, touched: VectorBuilderTouched, erase: boolean): VectorShapeBuilderResult {\n\t\tconst plan = planShapeBuilder(m, touched, erase);\n\t\tlet mergedId: string | null = null;\n\t\tconst created = new Map<string, string[]>();\n\t\tthis.change(erase ? 'Shape Builder Delete' : 'Shape Builder', () => {\n\t\t\tlet nodes = this.doc.nodes;\n\t\t\tm.entries.forEach((entry, i) => {\n\t\t\t\tconst node = entry.node;\n\t\t\t\tconst toLocal = (path: BezierPath) => transformBezierPath(path, invertMatrix(multiplyMatrices(nodeMatrix(node), entry.parent)));\n\t\t\t\tconst list: VectorNode[] = [];\n\t\t\t\tconst r = plan.replace.get(i);\n\t\t\t\tif (r === undefined) list.push(node);\n\t\t\t\telse if (r?.kind === 'fill') list.push({ ...node, fillRule: 'nonzero', subpaths: bezierToSubpaths(toLocal(r.path)) });\n\t\t\t\telse if (r?.kind === 'outline') list.push({ ...node, subpaths: bezierToSubpaths(toLocal(r.path)) });\n\t\t\t\telse if (r?.kind === 'pieces') {\n\t\t\t\t\tr.paths.forEach((path, k) =>\n\t\t\t\t\t\tlist.push({ ...node, id: k === 0 ? node.id : this.newId(), subpaths: bezierToSubpaths(toLocal(path)).map((sp) => ({ ...sp, closed: false })) })\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (plan.merged && plan.merged.style === i) {\n\t\t\t\t\tmergedId = this.newId();\n\t\t\t\t\tconst merged: VectorPathNode = {\n\t\t\t\t\t\t...vectorResultStyle(node, 'fill'),\n\t\t\t\t\t\ttype: 'path',\n\t\t\t\t\t\tid: mergedId,\n\t\t\t\t\t\tname: 'Shape',\n\t\t\t\t\t\t...(node.transform ? { transform: node.transform } : {}),\n\t\t\t\t\t\tsubpaths: bezierToSubpaths(toLocal(plan.merged.path))\n\t\t\t\t\t};\n\t\t\t\t\tif (plan.merged.currentFill || !hasPaint(merged.fill)) merged.fill = hasPaint(this.defaults.fill) ? this.defaults.fill! : '#808080';\n\t\t\t\t\tlist.push(merged);\n\t\t\t\t}\n\t\t\t\tif (r === undefined && list.length === 1) return;\n\t\t\t\tcreated.set(node.id, list.map((n) => n.id));\n\t\t\t\tnodes = spliceTree(nodes, node.id, list);\n\t\t\t});\n\t\t\tthis.setDoc(nodes);\n\t\t\t// The shapes stay selected so the next gesture works on them too.\n\t\t\tconst selection = this.selection.flatMap((id) => created.get(id) ?? [id]);\n\t\t\tif (mergedId && !selection.includes(mergedId)) selection.push(mergedId);\n\t\t\tthis.selection = selection.filter((id, k) => !!this.getNode(id) && selection.indexOf(id) === k);\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\tthis.builderCache = null;\n\t\treturn { ok: true, merged: mergedId, faces: touched.faces.length, lines: touched.lines.length, edges: erase ? vectorBuilderErasedEdges(m, touched).length : 0 };\n\t}\n\n\t/** The paint bucket's map of the visible outlines, cached per document and gap. */\n\tprivate fillMap(gap: number): PlanarMap | null {\n\t\tif (this.fillCache?.doc === this.doc && this.fillCache.gap === gap) return this.fillCache.map;\n\t\tconst map = vectorFillMap(this.doc, gap);\n\t\tthis.fillCache = { doc: this.doc, gap, map };\n\t\treturn map;\n\t}\n\n\t/**\n\t * Paint bucket: the region the visible outlines enclose around `point` (document units),\n\t * crossings split and islands left as holes, becomes a new path filled with the current fill\n\t * default, placed just below the selection (or on top when nothing is selected). `gap`\n\t * (default `editing.fillGap`) closes gaps up to that size between loose ends. Returns the new\n\t * path's id, or null when nothing encloses the point.\n\t */\n\tfillRegion(point: Vec2, options: { gap?: number } = {}): string | null {\n\t\tif (!Number.isFinite(point.x) || !Number.isFinite(point.y)) return null;\n\t\tconst region = this.fillMap(options.gap ?? this.editing.fillGap)?.regionAt([point.x, point.y]);\n\t\tif (!region?.length) return null;\n\t\t// Below the lowest selected node, in its parent.\n\t\tlet below: string | null = null;\n\t\tconst roots = new Set(this.selectedRoots());\n\t\twalkVectorNodes(this.doc.nodes, (n) => {\n\t\t\tif (!below && roots.has(n.id)) below = n.id;\n\t\t});\n\t\tconst subpaths = bezierToSubpaths(below ? this.intoParentSpace(below, region) : region);\n\t\tif (!subpaths.length) return null;\n\t\tconst id = this.newId();\n\t\tconst node: VectorPathNode = { id, type: 'path', name: 'Fill', ...this.styleFor(false), stroke: null, fillRule: 'nonzero', subpaths };\n\t\tif (!hasPaint(node.fill)) node.fill = '#808080';\n\t\tconst target = below as string | null;\n\t\tthis.change('Fill Region', () => {\n\t\t\tif (target) {\n\t\t\t\tconst current = this.getNode(target)!;\n\t\t\t\tthis.setDoc(spliceTree(this.doc.nodes, target, [node, current]));\n\t\t\t} else this.setDoc([...this.doc.nodes, node]);\n\t\t\tif (!target) this.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** Updates the Shape Builder / paint bucket highlight for the pointer at `p`; true when it changed. */\n\tprivate hoverBuilder(p: Vec2, alt: boolean): boolean {\n\t\tif (this.tool === 'paintBucket') {\n\t\t\tconst region = this.fillMap(this.editing.fillGap)?.regionAt([p.x, p.y]) ?? null;\n\t\t\tconst key = region ? bezierPathToSvg(region, { precision: 2 }) : null;\n\t\t\tif (key === this.builderHoverKey) return false;\n\t\t\tthis.builderHoverKey = key;\n\t\t\tthis.builderPreview = region ? { mode: 'fill', faces: [region], lines: [], trail: null } : null;\n\t\t\treturn true;\n\t\t}\n\t\tconst m = this.selectedRoots().length ? this.shapeBuilderMap() : null;\n\t\tconst hit = m ? vectorShapeBuilderHit(m, p, alt ? this.builderLineTolerance() : null) : null;\n\t\tconst key = hit ? `${alt ? 'erase' : 'merge'}:${JSON.stringify(hit)}` : null;\n\t\tif (key === this.builderHoverKey) return false;\n\t\tthis.builderHoverKey = key;\n\t\tconst touched: VectorBuilderTouched = { faces: [], lines: [], edges: [] };\n\t\taddBuilderHit(touched, hit);\n\t\tthis.builderPreview = hit && m ? this.builderPreviewOf(m, touched, alt, null) : null;\n\t\treturn true;\n\t}\n\n\tprivate builderPreviewOf(m: VectorShapeBuilderMap, touched: VectorBuilderTouched, erase: boolean, trail: Vec2[] | null): VectorBuilderPreview {\n\t\tconst faces = m.map.faces;\n\t\treturn {\n\t\t\tmode: erase ? 'erase' : 'merge',\n\t\t\tfaces: touched.faces.map((f) => faces[f].path),\n\t\t\t// Edges bounding a face touched too go with the face, so they are not shown apart.\n\t\t\tlines: [...touched.lines.map((k) => m.pieces[k].path), ...(erase ? vectorBuilderErasedEdges(m, touched).map((k) => m.edges[k].path) : [])],\n\t\t\ttrail\n\t\t};\n\t}\n\n\tprivate downBuilder(p: Vec2, input: VectorPointerInput) {\n\t\tthis.builderHoverKey = null;\n\t\tif (this.tool === 'paintBucket') {\n\t\t\tthis.fillRegion(p);\n\t\t\tthis.builderPreview = null;\n\t\t\tthis.hoverBuilder(p, false);\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst m = this.selectedRoots().length ? this.shapeBuilderMap() : null;\n\t\tif (!m) return;\n\t\tconst erase = !!input.alt;\n\t\tconst touched: VectorBuilderTouched = { faces: [], lines: [], edges: [] };\n\t\taddBuilderHit(touched, vectorShapeBuilderHit(m, p, erase ? this.builderLineTolerance() : null));\n\t\tthis.drag = { kind: 'builder', erase, points: [p], touched, map: m };\n\t\tthis.interaction = { type: 'builder', mode: erase ? 'erase' : 'merge', points: [p] };\n\t\tthis.builderPreview = this.builderPreviewOf(m, touched, erase, [p]);\n\t\tthis.notify();\n\t}\n\n\tprivate moveBuilder(drag: Extract<Drag, { kind: 'builder' }>, p: Vec2) {\n\t\tconst last = drag.points[drag.points.length - 1];\n\t\tif (Math.hypot(p.x - last.x, p.y - last.y) * this.viewport.zoom < 1) return;\n\t\tconst tol = drag.erase ? this.builderLineTolerance() : null;\n\t\tfor (const q of samplePolyline([last, p], 2 / this.viewport.zoom)) addBuilderHit(drag.touched, vectorShapeBuilderHit(drag.map, q, tol));\n\t\tdrag.points.push(p);\n\t\tthis.interaction = { type: 'builder', mode: drag.erase ? 'erase' : 'merge', points: drag.points.slice() };\n\t\tthis.builderPreview = this.builderPreviewOf(drag.map, drag.touched, drag.erase, drag.points.slice());\n\t\tthis.notify();\n\t}\n\n\tprivate upBuilder(drag: Extract<Drag, { kind: 'builder' }>) {\n\t\tthis.builderPreview = null;\n\t\tthis.builderHoverKey = null;\n\t\tif (!drag.touched.faces.length && !drag.touched.lines.length && !drag.touched.edges.length) {\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tthis.applyShapeBuilder(drag.map, drag.touched, drag.erase);\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Anchor editing\n\t// -------------------------------------------------------------------------\n\n\tprivate updateAnchors(doc: VectorDocument, refs: readonly VectorAnchorRef[], fn: (a: VectorAnchor, ref: VectorAnchorRef) => VectorAnchor): VectorDocument {\n\t\tconst byNode = new Map<string, VectorAnchorRef[]>();\n\t\tfor (const r of refs) byNode.set(r.nodeId, [...(byNode.get(r.nodeId) ?? []), r]);\n\t\tconst nodes = mapTree(doc.nodes, (n) => {\n\t\t\tconst list = byNode.get(n.id);\n\t\t\tif (!list || n.type !== 'path' || n.locked) return n;\n\t\t\tconst subpaths = n.subpaths.map((sp, s) => {\n\t\t\t\tconst hits = list.filter((r) => r.subpath === s);\n\t\t\t\tif (!hits.length) return sp;\n\t\t\t\treturn { closed: sp.closed, anchors: sp.anchors.map((a, i) => (hits.some((r) => r.index === i) ? fn(a, { nodeId: n.id, subpath: s, index: i }) : a)) };\n\t\t\t});\n\t\t\treturn { ...n, subpaths };\n\t\t});\n\t\treturn nodes === doc.nodes ? doc : { ...doc, nodes };\n\t}\n\n\t/** Moves anchors by a document-space offset (converted into each node's own space). */\n\tprivate movedAnchors(doc: VectorDocument, refs: readonly VectorAnchorRef[], dx: number, dy: number): VectorDocument {\n\t\tconst local = new Map<string, Vec2>();\n\t\tfor (const r of refs) if (!local.has(r.nodeId)) local.set(r.nodeId, applyMatrixLinear(invertMatrix(this.worldOf(r.nodeId, doc)), { x: dx, y: dy }));\n\t\treturn this.updateAnchors(doc, refs, (a, ref) => {\n\t\t\tconst d = local.get(ref.nodeId)!;\n\t\t\treturn translateAnchor(a, d.x, d.y);\n\t\t});\n\t}\n\n\t/**\n\t * Moves one anchor to a document position. Its handles follow; with `curvature` (default:\n\t * the `curvatureMoves` setting) its two segments bend instead, as the Curvature tool does.\n\t */\n\tmoveAnchor(ref: VectorAnchorRef, to: Vec2, options: { curvature?: boolean } = {}): VectorActionResult {\n\t\tconst a = getAnchorWorld(this.doc, ref);\n\t\tif (!a) return { ok: false, message: 'There is no such anchor.' };\n\t\tconst curvature = options.curvature ?? this.editing.curvatureMoves;\n\t\tthis.change('Move Point', () => {\n\t\t\tthis.doc = curvature ? this.curvatureMoved(this.doc, ref, to) : this.movedAnchors(this.doc, [ref], to.x - a.x, to.y - a.y);\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** `doc` with anchor `ref` moved to document point `to` by a Curvature-tool move. */\n\tprivate curvatureMoved(doc: VectorDocument, ref: VectorAnchorRef, to: Vec2): VectorDocument {\n\t\tconst node = findVectorNode(doc.nodes, ref.nodeId);\n\t\tif (node?.type !== 'path' || node.locked || !node.subpaths[ref.subpath]) return doc;\n\t\tconst q = this.toLocal(ref.nodeId, to, doc);\n\t\tconst subpaths = node.subpaths.map((sp, i) => (i === ref.subpath ? curvatureMoveAnchor(sp, ref.index, q) : sp));\n\t\tconst nodes = mapTree(doc.nodes, (n) => (n.id === ref.nodeId ? { ...node, subpaths } : n));\n\t\treturn { ...doc, nodes };\n\t}\n\n\t/** `doc` with segment `ref` bent through document point `p` at parameter `t`. */\n\tprivate bent(doc: VectorDocument, ref: VectorSegmentRef, t: number, p: Vec2): VectorDocument {\n\t\tconst node = findVectorNode(doc.nodes, ref.nodeId);\n\t\tif (node?.type !== 'path' || node.locked || !node.subpaths[ref.subpath]) return doc;\n\t\tconst q = this.toLocal(ref.nodeId, p, doc);\n\t\tconst subpaths = node.subpaths.map((sp, i) => (i === ref.subpath ? bendSubpathSegment(sp, ref.segment, t, q) : sp));\n\t\treturn { ...doc, nodes: mapTree(doc.nodes, (n) => (n.id === ref.nodeId ? { ...node, subpaths } : n)) };\n\t}\n\n\t/**\n\t * Bends a segment so the curve passes through document point `to` at parameter `t`\n\t * (default 0.5, clamped to 0.05–0.95); see `bendSegment` in `pathEdit`.\n\t */\n\tbendSegment(ref: VectorSegmentRef, to: Vec2, t = 0.5): VectorActionResult {\n\t\tconst node = this.getNode(ref.nodeId);\n\t\tconst sp = node?.type === 'path' ? node.subpaths[ref.subpath] : undefined;\n\t\tif (!sp || ref.segment < 0 || ref.segment >= (sp.closed ? sp.anchors.length : sp.anchors.length - 1) || sp.anchors.length < 2) return { ok: false, message: 'There is no such segment.' };\n\t\tif (node!.locked) return { ok: false, message: 'The path is locked.' };\n\t\tthis.change('Bend Segment', () => {\n\t\t\tthis.doc = this.bent(this.doc, ref, t, to);\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Moves one handle to a document position (see `moveAnchorHandle` for how the opposite handle follows). */\n\tmoveHandle(ref: VectorHandleRef, to: Vec2, options: { breakHandles?: boolean } = {}): VectorActionResult {\n\t\tif (!getAnchor(this.doc, ref)) return { ok: false, message: 'There is no such anchor.' };\n\t\tconst local = this.toLocal(ref.nodeId, to);\n\t\tthis.change('Move Handle', () => {\n\t\t\tthis.doc = this.updateAnchors(this.doc, [ref], (a) => moveAnchorHandle(a, ref.handle, local, options.breakHandles));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Converts the selected (or given) anchors between corner, smooth and symmetric. */\n\tconvertAnchors(type: VectorAnchorType, refs: readonly VectorAnchorRef[] = this.anchors): VectorActionResult {\n\t\tif (!refs.length) return { ok: false, message: 'Select points to convert.' };\n\t\tthis.change(type === 'corner' ? 'Make Corner' : 'Make Smooth', () => {\n\t\t\tconst nodes = mapTree(this.doc.nodes, (n) => {\n\t\t\t\tif (n.type !== 'path') return n;\n\t\t\t\tconst mine = refs.filter((r) => r.nodeId === n.id);\n\t\t\t\tif (!mine.length) return n;\n\t\t\t\tconst subpaths = n.subpaths.map((sp, s) => mine.filter((r) => r.subpath === s).reduce((acc, r) => convertAnchor(acc, r.index, type), sp));\n\t\t\t\treturn { ...n, subpaths };\n\t\t\t});\n\t\t\tthis.setDoc(nodes);\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Adds an anchor on the nearest segment of `nodeId` to document point `point` (shape unchanged). */\n\tinsertAnchorAt(nodeId: string, point: Vec2): VectorAnchorRef | null {\n\t\tconst node = this.getNode(nodeId);\n\t\tif (node?.type !== 'path') return null;\n\t\tconst hit = nearestSegment(node, this.toLocal(nodeId, point));\n\t\tif (!hit) return null;\n\t\tconst { subpath, index } = insertAnchor(node.subpaths[hit.subpath], hit.segment, hit.t);\n\t\tconst ref = { nodeId, subpath: hit.subpath, index };\n\t\tthis.change('Add Point', () => {\n\t\t\tthis.replaceNode(nodeId, { ...node, subpaths: node.subpaths.map((sp, i) => (i === hit.subpath ? subpath : sp)) });\n\t\t\tthis.editingId = nodeId;\n\t\t\tthis.anchors = [ref];\n\t\t});\n\t\treturn ref;\n\t}\n\n\t/**\n\t * Deletes the selected (or given) anchors; subpaths left with one anchor and empty paths go\n\t * too. With `keepShape` (default: the `keepShapeOnDelete` setting, on) the neighbours'\n\t * handles are refitted so the path keeps its shape; off, the neighbours are joined as they are.\n\t */\n\tdeleteAnchors(refs: readonly VectorAnchorRef[] = this.anchors, options: { keepShape?: boolean } = {}): VectorActionResult {\n\t\tif (!refs.length) return { ok: false, message: 'Select points to delete.' };\n\t\tconst keepShape = options.keepShape ?? this.editing.keepShapeOnDelete;\n\t\tthis.change('Delete Points', () => {\n\t\t\tconst nodes = mapTree(this.doc.nodes, (n) => {\n\t\t\t\tif (n.type !== 'path' || n.locked) return n;\n\t\t\t\tconst mine = refs.filter((r) => r.nodeId === n.id);\n\t\t\t\tif (!mine.length) return n;\n\t\t\t\tconst subpaths = n.subpaths\n\t\t\t\t\t.map((sp, s) => {\n\t\t\t\t\t\tconst indices = mine.filter((r) => r.subpath === s).map((r) => r.index);\n\t\t\t\t\t\tif (!indices.length) return sp;\n\t\t\t\t\t\treturn keepShape ? removeAnchorsKeepingShape(sp, indices) : removeAnchors(sp, indices);\n\t\t\t\t\t})\n\t\t\t\t\t.filter((sp) => sp.anchors.length > 1);\n\t\t\t\treturn subpaths.length ? { ...n, subpaths } : null;\n\t\t\t});\n\t\t\tthis.setDoc(nodes);\n\t\t\tthis.anchors = [];\n\t\t\tthis.selection = this.selection.filter((id) => this.getNode(id));\n\t\t\tif (this.editingId && !this.getNode(this.editingId)) this.editingId = null;\n\t\t\tif (this.pen && !this.getNode(this.pen.nodeId)) this.pen = null;\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Opens or closes the subpaths that contain selected anchors (or all subpaths of `nodeId`). */\n\tsetClosed(closed: boolean, nodeId?: string): VectorActionResult {\n\t\tconst targets = nodeId\n\t\t\t? (() => {\n\t\t\t\t\tconst n = this.getNode(nodeId);\n\t\t\t\t\treturn n?.type === 'path' ? n.subpaths.map((_, s) => ({ nodeId, subpath: s })) : [];\n\t\t\t\t})()\n\t\t\t: this.anchors.map((r) => ({ nodeId: r.nodeId, subpath: r.subpath }));\n\t\tif (!targets.length) return { ok: false, message: 'Select a path or points.' };\n\t\tthis.change(closed ? 'Close Path' : 'Open Path', () => {\n\t\t\tthis.setDoc(\n\t\t\t\tmapTree(this.doc.nodes, (n) =>\n\t\t\t\t\tn.type === 'path' && targets.some((t) => t.nodeId === n.id)\n\t\t\t\t\t\t? { ...n, subpaths: n.subpaths.map((sp, s) => (targets.some((t) => t.nodeId === n.id && t.subpath === s) ? { ...sp, closed } : sp)) }\n\t\t\t\t\t\t: n\n\t\t\t\t)\n\t\t\t);\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Reverses the direction of every subpath of the selected paths. */\n\treversePaths(ids: readonly string[] = this.selection): VectorActionResult {\n\t\tconst set = new Set(ids.flatMap((id) => (this.getNode(id) ? collectPathNodes(this.getNode(id)!) : [])).map((p) => p.id));\n\t\tif (!set.size) return { ok: false, message: 'Select a path.' };\n\t\tthis.change('Reverse Path', () => {\n\t\t\tthis.setDoc(\n\t\t\t\tmapTree(this.doc.nodes, (n) =>\n\t\t\t\t\tn.type === 'path' && set.has(n.id)\n\t\t\t\t\t\t? { ...n, subpaths: n.subpaths.map(reverseSubpath), ...(n.strokeWidthProfile?.length ? { strokeWidthProfile: reverseWidthProfile(n.strokeWidthProfile) } : {}) }\n\t\t\t\t\t\t: n\n\t\t\t\t)\n\t\t\t);\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Path clean-up, Scissors, Knife and Eraser\n\t// -------------------------------------------------------------------------\n\n\t/** Unlocked path nodes in the given roots (default: the paths being edited, else the selection). */\n\tprivate pathTargets(ids?: readonly string[]): VectorPathNode[] {\n\t\tconst roots = ids ?? (this.tool === 'direct' && this.anchors.length ? [...new Set(this.anchors.map((r) => r.nodeId))] : this.selection);\n\t\tconst out = new Map<string, VectorPathNode>();\n\t\tfor (const id of roots) {\n\t\t\tconst node = this.getNode(id);\n\t\t\tif (node && !node.locked) for (const p of collectPathNodes(node)) if (!p.locked) out.set(p.id, p);\n\t\t}\n\t\t// Document order, bottom to top.\n\t\tconst order: string[] = [];\n\t\twalkVectorNodes(this.doc.nodes, (n) => {\n\t\t\tif (out.has(n.id)) order.push(n.id);\n\t\t});\n\t\treturn order.map((id) => out.get(id)!);\n\t}\n\n\t/** Subpaths of node `from` mapped into node `to`'s own space. */\n\tprivate subpathsIn(from: VectorPathNode, to: string): VectorSubpath[] {\n\t\tconst m = multiplyMatrices(this.worldOf(from.id), invertMatrix(this.worldOf(to)));\n\t\tif (isRoot(m)) return from.subpaths;\n\t\tconst map = (p: Vec2) => applyMatrix(m, p);\n\t\treturn from.subpaths.map((sp) => ({\n\t\t\tclosed: sp.closed,\n\t\t\tanchors: sp.anchors.map((a) => ({ ...map(a), ...(a.in ? { in: map(a.in) } : {}), ...(a.out ? { out: map(a.out) } : {}), ...(a.type ? { type: a.type } : {}), ...(a.corner ? { corner: { ...a.corner } } : {}) }))\n\t\t}));\n\t}\n\n\t/**\n\t * Replaces a path with pieces: each entry becomes one path (the first keeps the node's id and\n\t * place, the others go just above it with the same style). No pieces removes the path.\n\t * Returns the ids of the paths now standing in for it.\n\t */\n\tprivate replaceWithPieces(node: VectorPathNode, pieces: readonly VectorSubpath[][]): string[] {\n\t\tconst nodes = pieces.filter((p) => p.length).map((subpaths, i) => ({ ...node, id: i === 0 ? node.id : this.newId(), subpaths }) as VectorPathNode);\n\t\tthis.setDoc(spliceTree(this.doc.nodes, node.id, nodes));\n\t\treturn nodes.map((n) => n.id);\n\t}\n\n\t/** Clears anchor selections, editing and pen state that no longer point at anything. */\n\tprivate dropStaleRefs() {\n\t\tthis.selection = this.selection.filter((id) => this.getNode(id));\n\t\tthis.anchors = this.anchors.filter((r) => getAnchor(this.doc, r));\n\t\tif (this.editingId && !this.getNode(this.editingId)) this.editingId = null;\n\t\tif (this.pen && !this.getNode(this.pen.nodeId)) this.pen = null;\n\t}\n\n\t/**\n\t * Join: the open subpaths of the selected (or given) paths become one open path, joined\n\t * nearest endpoints first (ends within `tolerance` document units merge into one anchor,\n\t * others get a straight segment); a single open subpath is closed. The result replaces the\n\t * lowest path, in its space and style. Default tolerance: the pick distance.\n\t */\n\tjoinPaths(options: { ids?: readonly string[]; tolerance?: number } = {}): VectorActionResult {\n\t\tconst paths = this.pathTargets(options.ids);\n\t\tif (!paths.length) return { ok: false, message: 'Select paths to join.' };\n\t\tconst base = paths[0];\n\t\tconst all = paths.flatMap((p) => this.subpathsIn(p, base.id));\n\t\tif (!all.some((sp) => !sp.closed && sp.anchors.length > 1)) return { ok: false, message: 'There are no open paths to join.' };\n\t\tconst subpaths = joinSubpaths(all, options.tolerance ?? this.tolerance());\n\t\tconst drop = new Set(paths.slice(1).map((p) => p.id));\n\t\tthis.change('Join', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === base.id ? { ...base, subpaths } : drop.has(n.id) ? null : n)));\n\t\t\tthis.selection = [base.id];\n\t\t\tthis.anchors = [];\n\t\t\tif (this.editingId && drop.has(this.editingId)) this.editingId = base.id;\n\t\t\tthis.dropStaleRefs();\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Average: moves the selected (or given) anchors onto their average position along `axis`, in document space. */\n\taverageAnchors(axis: VectorAverageAxis = 'both', refs: readonly VectorAnchorRef[] = this.anchors): VectorActionResult {\n\t\tconst points = refs.map((r) => ({ ref: r, at: getAnchorWorld(this.doc, r) })).filter((p): p is { ref: VectorAnchorRef; at: NonNullable<ReturnType<typeof getAnchorWorld>> } => !!p.at);\n\t\tif (points.length < 2) return { ok: false, message: 'Select at least two points to average.' };\n\t\tconst cx = points.reduce((s, p) => s + p.at.x, 0) / points.length;\n\t\tconst cy = points.reduce((s, p) => s + p.at.y, 0) / points.length;\n\t\tthis.change('Average', () => {\n\t\t\tfor (const { ref, at } of points) {\n\t\t\t\tconst dx = axis === 'horizontal' ? 0 : cx - at.x;\n\t\t\t\tconst dy = axis === 'vertical' ? 0 : cy - at.y;\n\t\t\t\tif (dx || dy) this.doc = this.movedAnchors(this.doc, [ref], dx, dy);\n\t\t\t}\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Applies `fn` to every subpath of the target paths as one undo step. */\n\tprivate editSubpaths(label: string, ids: readonly string[] | undefined, fn: (sp: VectorSubpath, node: VectorPathNode, index: number) => VectorSubpath): VectorActionResult {\n\t\tconst paths = this.pathTargets(ids);\n\t\tif (!paths.length) return { ok: false, message: 'Select a path.' };\n\t\tconst set = new Set(paths.map((p) => p.id));\n\t\tthis.change(label, () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.type === 'path' && set.has(n.id) ? { ...n, subpaths: n.subpaths.map((sp, i) => fn(sp, n, i)) } : n)));\n\t\t\tthis.dropStaleRefs();\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * Remove Redundant Points: drops anchors of the selected (or given) paths that can go\n\t * without moving the outline more than `tolerance` document units (default 0.5).\n\t */\n\tremoveRedundantPoints(options: { ids?: readonly string[]; tolerance?: number } = {}): VectorActionResult {\n\t\tconst tolerance = options.tolerance ?? 0.5;\n\t\treturn this.editSubpaths('Remove Redundant Points', options.ids, (sp) => removeRedundantSubpathPoints(sp, tolerance));\n\t}\n\n\t/**\n\t * Smooth: pulls handles towards smooth tangents by `amount` (0–1, default 1). With the direct\n\t * tool and points selected, only those points change.\n\t */\n\tsmoothPaths(options: { ids?: readonly string[]; amount?: number; refs?: readonly VectorAnchorRef[] } = {}): VectorActionResult {\n\t\tconst refs = options.refs ?? (!options.ids && this.tool === 'direct' && this.anchors.length ? this.anchors : null);\n\t\tconst ids = options.ids ?? (refs ? [...new Set(refs.map((r) => r.nodeId))] : undefined);\n\t\treturn this.editSubpaths('Smooth', ids, (sp, node, i) => {\n\t\t\tconst only = refs ? new Set(refs.filter((r) => r.nodeId === node.id && r.subpath === i).map((r) => r.index)) : undefined;\n\t\t\treturn only && !only.size ? sp : smoothSubpath(sp, options.amount ?? 1, only);\n\t\t});\n\t}\n\n\t/** Splits a cut subpath into pieces: separate paths for a one-subpath node, subpaths in place for a compound one. */\n\tprivate applyCuts(node: VectorPathNode, cut: (sp: VectorSubpath, index: number) => VectorSubpath[] | null): string[] | null {\n\t\tconst results = node.subpaths.map((sp, i) => cut(sp, i));\n\t\tif (results.every((r) => !r)) return null;\n\t\tconst subpaths = node.subpaths.flatMap((sp, i) => results[i] ?? [sp]);\n\t\tif (node.subpaths.length === 1) return this.replaceWithPieces(node, subpaths.map((sp) => [sp]));\n\t\tthis.replaceNode(node.id, { ...node, subpaths });\n\t\treturn [node.id];\n\t}\n\n\t/**\n\t * Scissors: cuts path `nodeId` at the outline point nearest document point `point` (at an\n\t * anchor when within the pick distance of one). A closed subpath opens there; an open one\n\t * splits in two (a one-subpath path becomes two paths). Returns the ids of the paths.\n\t */\n\tcutPathAt(nodeId: string, point: Vec2): string[] | null {\n\t\tconst node = this.getNode(nodeId);\n\t\tif (node?.type !== 'path' || node.locked) return null;\n\t\tconst local = this.toLocal(nodeId, point);\n\t\tconst hit = nearestSegment(node, local);\n\t\tif (!hit) return null;\n\t\tconst sp = node.subpaths[hit.subpath];\n\t\tconst n = sp.anchors.length;\n\t\tconst near = [hit.segment, (hit.segment + 1) % n].find((i) => Math.hypot(sp.anchors[i].x - hit.point.x, sp.anchors[i].y - hit.point.y) <= this.tolerance());\n\t\tif (!sp.closed && n > 1 && (near === 0 || near === n - 1) && !(hit.t > 1e-6 && hit.t < 1 - 1e-6)) return null;\n\t\tlet ids: string[] | null = null;\n\t\tthis.change('Cut Path', () => {\n\t\t\tids = this.applyCuts(node, (s, i) => (i !== hit.subpath ? null : near !== undefined ? cutSubpathAt(s, [near]) : splitSubpathAt(s, hit.segment, hit.t)));\n\t\t\tif (ids) {\n\t\t\t\tthis.selection = ids;\n\t\t\t\tthis.anchors = [];\n\t\t\t\tif (this.editingId === nodeId) this.editingId = null;\n\t\t\t}\n\t\t\tthis.dropStaleRefs();\n\t\t});\n\t\treturn ids;\n\t}\n\n\t/** Scissors on the selected (or given) anchors: each cuts its subpath there (see `cutPathAt`). */\n\tcutAtAnchors(refs: readonly VectorAnchorRef[] = this.anchors): VectorActionResult {\n\t\tconst byNode = new Map<string, VectorAnchorRef[]>();\n\t\tfor (const r of refs) if (getAnchor(this.doc, r)) byNode.set(r.nodeId, [...(byNode.get(r.nodeId) ?? []), r]);\n\t\tif (!byNode.size) return { ok: false, message: 'Select points to cut at.' };\n\t\tconst created: string[] = [];\n\t\tthis.change('Cut Path', () => {\n\t\t\tfor (const [id, list] of byNode) {\n\t\t\t\tconst node = this.getNode(id) as VectorPathNode;\n\t\t\t\tif (node.locked) continue;\n\t\t\t\tconst ids = this.applyCuts(node, (sp, i) => {\n\t\t\t\t\tconst at = list.filter((r) => r.subpath === i).map((r) => r.index);\n\t\t\t\t\tconst pieces = at.length ? cutSubpathAt(sp, at) : null;\n\t\t\t\t\treturn pieces && !(pieces.length === 1 && pieces[0].closed === sp.closed) ? pieces : null;\n\t\t\t\t});\n\t\t\t\tif (ids) created.push(...ids);\n\t\t\t}\n\t\t\tif (created.length) this.selection = created;\n\t\t\tthis.anchors = [];\n\t\t\tthis.dropStaleRefs();\n\t\t});\n\t\treturn created.length ? { ok: true } : { ok: false, message: 'Points at the ends of open paths do not cut.' };\n\t}\n\n\t/** Visible, unlocked paths a stroke over `points` can reach: in `ids`, the selection, or (with neither) any whose bounds it crosses. */\n\tprivate strokeTargets(points: readonly Vec2[], ids: readonly string[] | undefined, pad: number): VectorPathNode[] {\n\t\tconst blocked = new Set<string>();\n\t\twalkVectorNodes(this.doc.nodes, (n, parent) => {\n\t\t\tif (n.visible === false || n.locked || (parent && blocked.has(parent.id))) blocked.add(n.id);\n\t\t});\n\t\tconst visible = (p: VectorPathNode) => !blocked.has(p.id);\n\t\tif (ids?.length || this.selection.length) return this.pathTargets(ids ?? this.selection).filter(visible);\n\t\tconst box = polylineBounds(points.map(toPoint), pad);\n\t\tif (!box) return [];\n\t\tconst out: VectorPathNode[] = [];\n\t\twalkVectorNodes(this.doc.nodes, (n) => {\n\t\t\tif (n.type !== 'path' || !visible(n)) return;\n\t\t\tconst b = vectorNodeBounds(n, vectorParentMatrix(this.doc.nodes, n.id) ?? IDENTITY_MATRIX);\n\t\t\tif (b && boundsIntersect(b, box)) out.push(n);\n\t\t});\n\t\treturn out;\n\t}\n\n\t/** A stroke's points in node `id`'s own space. */\n\tprivate localPoints(id: string, points: readonly Vec2[]): Point[] {\n\t\tconst inv = invertMatrix(this.worldOf(id));\n\t\treturn points.map((p) => toPoint(applyMatrix(inv, p)));\n\t}\n\n\t/**\n\t * Knife: cuts closed paths along the polyline `points` (document space) wherever it crosses\n\t * them from edge to edge, with no gap between the pieces. It cuts the given paths, the\n\t * selection, or (with neither) every path it crosses. Returns the ids of the pieces (empty\n\t * when nothing was cut).\n\t */\n\tknife(points: readonly Vec2[], options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst pts = simplifyPolyline(points.map(toPoint), 0.25 / this.viewport.zoom).map(([x, y]) => ({ x, y }));\n\t\tif (pts.length < 2) return [];\n\t\tconst plan: { node: VectorPathNode; pieces: VectorSubpath[][] }[] = [];\n\t\tfor (const node of this.strokeTargets(pts, options.ids, 0)) {\n\t\t\tconst closed = node.subpaths.filter((sp) => sp.anchors.length > 1);\n\t\t\tif (!closed.length || closed.some((sp) => !sp.closed)) continue;\n\t\t\tconst result = knifePath(subpathsToBezier(closed), node.fillRule ?? 'nonzero', this.localPoints(node.id, pts));\n\t\t\tif (result) plan.push({ node, pieces: result.map((path) => bezierToSubpaths(path)) });\n\t\t}\n\t\tif (!plan.length) return [];\n\t\tconst created: string[] = [];\n\t\tthis.change('Knife', () => {\n\t\t\tfor (const { node, pieces } of plan) created.push(...this.replaceWithPieces(node, pieces));\n\t\t\tthis.selection = created;\n\t\t\tthis.anchors = [];\n\t\t\tthis.dropStaleRefs();\n\t\t});\n\t\treturn created;\n\t}\n\n\t/**\n\t * Eraser: rubs out a round stroke of diameter `size` (default: the `eraserSize` setting)\n\t * along `points` (document space). Filled closed paths lose that area (pieces that come\n\t * apart become separate paths); outlines are cut where they pass under it. It erases from\n\t * the given paths, the selection, or (with neither) every path it reaches. Returns the ids\n\t * of the paths it changed or created.\n\t */\n\terase(points: readonly Vec2[], options: { ids?: readonly string[]; size?: number } = {}): string[] {\n\t\tconst width = options.size ?? this.editing.eraserSize;\n\t\tif (!(width > 0) || !points.length) return [];\n\t\tconst pts = simplifyPolyline(points.map(toPoint), Math.min(width * 0.05, 0.25 / this.viewport.zoom));\n\t\tconst region = eraserRegion(pts, width);\n\t\tif (!region.length) return [];\n\t\tconst plan: { node: VectorPathNode; pieces: VectorSubpath[][] }[] = [];\n\t\tconst rb = bezierPathBounds(region);\n\t\tconst reach = rb ? [{ x: rb.minX, y: rb.minY }, { x: rb.maxX, y: rb.maxY }] : points;\n\t\tfor (const node of this.strokeTargets(reach, options.ids, 0)) {\n\t\t\tconst local = transformBezierPath(region, invertMatrix(this.worldOf(node.id)));\n\t\t\tconst live = node.subpaths.filter((sp) => sp.anchors.length > 1);\n\t\t\tif (!live.length) continue;\n\t\t\tif (hasPaint(node.fill) && live.every((sp) => sp.closed)) {\n\t\t\t\tconst faces = eraseFromFill(subpathsToBezier(live), node.fillRule ?? 'nonzero', local);\n\t\t\t\tif (faces) plan.push({ node, pieces: faces.map((f) => bezierToSubpaths(f)) });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst results = live.map((sp) => eraseFromOutline(sp, local));\n\t\t\tif (results.every((r) => !r)) continue;\n\t\t\tconst subpaths = live.flatMap((sp, i) => results[i] ?? [sp]);\n\t\t\tplan.push({ node, pieces: live.length === 1 ? subpaths.map((sp) => [sp]) : [subpaths] });\n\t\t}\n\t\tif (!plan.length) return [];\n\t\tconst changed: string[] = [];\n\t\tthis.change('Erase', () => {\n\t\t\tfor (const { node, pieces } of plan) changed.push(...this.replaceWithPieces(node, pieces));\n\t\t\tthis.anchors = [];\n\t\t\tthis.dropStaleRefs();\n\t\t\tif (this.selection.length || options.ids) this.selection = [...new Set([...this.selection, ...changed.filter((id) => this.getNode(id))])];\n\t\t});\n\t\treturn changed;\n\t}\n\n\t/** Ends the pen path being drawn (a lone anchor is removed). */\n\tfinishPath(): void {\n\t\tif (!this.pen) return;\n\t\tconst node = this.getNode(this.pen.nodeId) as VectorPathNode | undefined;\n\t\tconst sp = node?.subpaths[this.pen.subpath];\n\t\tconst pen = this.pen;\n\t\tthis.pen = null;\n\t\tthis.pointer = null;\n\t\tif (node && sp && sp.anchors.length < 2) {\n\t\t\tconst subpaths = node.subpaths.filter((_, i) => i !== pen.subpath);\n\t\t\tthis.change('Delete Path', () => {\n\t\t\t\tthis.replaceNode(node.id, subpaths.length ? { ...node, subpaths } : null);\n\t\t\t\tif (!subpaths.length) {\n\t\t\t\t\tthis.selection = this.selection.filter((id) => id !== node.id);\n\t\t\t\t\tif (this.editingId === node.id) this.editingId = null;\n\t\t\t\t}\n\t\t\t\tthis.anchors = [];\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tthis.notify();\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Clipboard\n\t// -------------------------------------------------------------------------\n\n\t/** Copies the selection. Returns JSON text for the system clipboard (or null). */\n\tcopy(): string | null {\n\t\tconst nodes = this.selectedRoots()\n\t\t\t.map((id) => this.getNode(id))\n\t\t\t.filter((n): n is VectorNode => !!n)\n\t\t\t.map((n) => cloneVectorNode(n));\n\t\tif (!nodes.length) return null;\n\t\tthis.clipboard = nodes;\n\t\treturn JSON.stringify({ type: VECTOR_CLIPBOARD_TYPE, nodes });\n\t}\n\n\tcut(): string | null {\n\t\tconst text = this.copy();\n\t\tif (text) this.removeNodes(this.selectedRoots(), { label: 'Cut' });\n\t\treturn text;\n\t}\n\n\t/**\n\t * Pastes `text` (from `copy()`, or SVG markup) or the internal clipboard, offset by 10, 10.\n\t * Returns the new ids.\n\t */\n\tpaste(text?: string, options: { offset?: Vec2 } = {}): string[] {\n\t\tlet nodes: VectorNode[] | null = null;\n\t\tif (text) {\n\t\t\tconst trimmed = text.trim();\n\t\t\tif (trimmed.startsWith('{')) {\n\t\t\t\ttry {\n\t\t\t\t\tconst data = JSON.parse(trimmed);\n\t\t\t\t\tif (data?.type === VECTOR_CLIPBOARD_TYPE && Array.isArray(data.nodes)) nodes = data.nodes;\n\t\t\t\t} catch {\n\t\t\t\t\tnodes = null;\n\t\t\t\t}\n\t\t\t} else if (/<svg[\\s>]/i.test(trimmed)) {\n\t\t\t\ttry {\n\t\t\t\t\tnodes = parseVectorSvg(trimmed).nodes;\n\t\t\t\t} catch {\n\t\t\t\t\tnodes = null;\n\t\t\t\t}\n\t\t\t}\n\t\t} else nodes = this.clipboard;\n\t\tif (!nodes?.length) return [];\n\t\tconst offset = options.offset ?? (text && !text.trim().startsWith('{') ? { x: 0, y: 0 } : { x: 10, y: 10 });\n\t\tconst copies = nodes.map((n) => transformVectorNode(this.freshIds(n), translateMatrix(offset.x, offset.y)));\n\t\tthis.change('Paste', () => {\n\t\t\tthis.setDoc([...this.doc.nodes, ...copies]);\n\t\t\tthis.selection = copies.map((n) => n.id);\n\t\t\tthis.anchors = [];\n\t\t});\n\t\tif (!text) this.clipboard = copies.map((n) => cloneVectorNode(n));\n\t\treturn copies.map((n) => n.id);\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Undo\n\t// -------------------------------------------------------------------------\n\n\tundo(): boolean {\n\t\tthis.cancelDrag();\n\t\tthis.pen = null;\n\t\treturn this.history.undo();\n\t}\n\n\tredo(): boolean {\n\t\tthis.cancelDrag();\n\t\treturn this.history.redo();\n\t}\n\n\t/** Deletes selected anchors (direct tool) or nodes. */\n\tdeleteSelection(): VectorActionResult {\n\t\tif (this.tool === 'direct' && this.anchors.length) return this.deleteAnchors();\n\t\treturn this.removeNodes(this.selectedRoots());\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Pointer input\n\t// -------------------------------------------------------------------------\n\n\t/** The fields the anchor / handle helpers read, without building a full state. */\n\tprivate view() {\n\t\treturn { document: this.doc, selection: this.selection, editingId: this.editingId, tool: this.tool, pen: this.pen, selectedAnchors: this.anchors };\n\t}\n\n\tprivate tolerance() {\n\t\treturn this.hitRadius / this.viewport.zoom;\n\t}\n\n\tprivate hitTransformHandle(screen: Vec2): VectorTransformHandle | null {\n\t\tif (this.tool !== 'select' || !this.selection.length) return null;\n\t\tconst frame = this.frameOf(this.selectedRoots());\n\t\tif (!frame) return null;\n\t\tconst { box, handles } = getVectorSelectionBoxGeometry(frame, this.viewport);\n\t\tconst r = this.hitRadius + 2;\n\t\t// Tiny boxes: only corners and rotation, so the box can still be grabbed.\n\t\tconst usable = box.width < 2 * r || box.height < 2 * r ? handles.filter((h) => h.handle.length === 2 || h.handle === 'rotate') : handles;\n\t\tfor (const h of usable) if (Math.hypot(h.screen.x - screen.x, h.screen.y - screen.y) <= r * Math.SQRT2) return h.handle;\n\t\treturn null;\n\t}\n\n\tprivate hitHandle(screen: Vec2): VectorHandleRef | null {\n\t\tfor (const ref of getVisibleHandles(this.view()).reverse()) {\n\t\t\tconst a = getAnchorWorld(this.doc, ref);\n\t\t\tconst h = a?.[ref.handle];\n\t\t\tif (!h) continue;\n\t\t\tconst s = this.documentToScreen(h);\n\t\t\tif (Math.hypot(s.x - screen.x, s.y - screen.y) <= this.hitRadius) return ref;\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate cornerView() {\n\t\treturn { ...this.view(), viewport: this.viewport };\n\t}\n\n\tprivate hitCornerWidget(screen: Vec2): VectorCornerWidget | null {\n\t\tlet best: VectorCornerWidget | null = null;\n\t\tlet bestD = Infinity;\n\t\tfor (const w of getVectorCornerWidgets(this.cornerView())) {\n\t\t\tconst s = this.documentToScreen(w);\n\t\t\tconst d = Math.hypot(s.x - screen.x, s.y - screen.y);\n\t\t\tif (d <= this.hitRadius && d < bestD) {\n\t\t\t\tbest = w;\n\t\t\t\tbestD = d;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\tprivate hitAnchor(screen: Vec2, refs: readonly VectorAnchorRef[]): VectorAnchorRef | null {\n\t\tlet best: VectorAnchorRef | null = null;\n\t\tlet bestD = Infinity;\n\t\tfor (const ref of refs) {\n\t\t\tconst a = getAnchorWorld(this.doc, ref);\n\t\t\tif (!a) continue;\n\t\t\tconst s = this.documentToScreen(a);\n\t\t\tconst d = Math.hypot(s.x - screen.x, s.y - screen.y);\n\t\t\tif (d <= this.hitRadius && d < bestD) {\n\t\t\t\tbest = ref;\n\t\t\t\tbestD = d;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\t/** Deepest unlocked visible path under `p`. */\n\t/** Live groups being edited (selected, or holding the selection or the edited path): their children are hit as stored. */\n\tprivate enteredGroups(): Set<string> {\n\t\treturn vectorEnteredGroups(this.doc.nodes, this.editingId ? [...this.selection, this.editingId] : this.selection);\n\t}\n\n\tprivate hitPath(p: Vec2): VectorPathNode | null {\n\t\tconst tol = this.tolerance();\n\t\tconst entered = this.enteredGroups();\n\t\tconst search = (list: readonly VectorNode[], parent: AffineMatrix): VectorPathNode | null => {\n\t\t\tfor (let i = list.length - 1; i >= 0; i--) {\n\t\t\t\tconst n = list[i];\n\t\t\t\tif (n.visible === false || n.locked) continue;\n\t\t\t\tif (n.type === 'group') {\n\t\t\t\t\t// A live group not being edited is hit where it draws (a click in a subtracted\n\t\t\t\t\t// hole goes through), then picks the child under the pointer.\n\t\t\t\t\tif (hasOwnVectorAppearance(n) && !entered.has(n.id) && !hitTestVectorNode(n, p, tol, parent, { entered })) continue;\n\t\t\t\t\tconst found = search(n.children, multiplyMatrices(nodeMatrix(n), parent));\n\t\t\t\t\tif (found) return found;\n\t\t\t\t} else if (hitTestVectorNode(n, p, tol, parent, { entered })) return n;\n\t\t\t}\n\t\t\treturn null;\n\t\t};\n\t\treturn search(this.doc.nodes, IDENTITY_MATRIX);\n\t}\n\n\t/** Endpoints of open subpaths of unlocked visible paths (pen tool resumes from them). */\n\tprivate openEndpoints(): VectorAnchorRef[] {\n\t\tconst out: VectorAnchorRef[] = [];\n\t\twalkVectorNodes(this.doc.nodes, (n) => {\n\t\t\tif (n.type !== 'path' || n.locked || n.visible === false) return;\n\t\t\tn.subpaths.forEach((sp, s) => {\n\t\t\t\tif (sp.closed || sp.anchors.length < 1) return;\n\t\t\t\tout.push({ nodeId: n.id, subpath: s, index: 0 });\n\t\t\t\tif (sp.anchors.length > 1) out.push({ nodeId: n.id, subpath: s, index: sp.anchors.length - 1 });\n\t\t\t});\n\t\t});\n\t\treturn out;\n\t}\n\n\tprivate computeHover(screen: Vec2): VectorHover | null {\n\t\tconst p = this.screenToDocument(screen);\n\t\tswitch (this.tool) {\n\t\t\tcase 'select': {\n\t\t\t\tconst handle = this.hitTransformHandle(screen);\n\t\t\t\tif (handle) return { nodeId: null, transformHandle: handle };\n\t\t\t\tconst node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance(), { entered: this.enteredGroups() });\n\t\t\t\treturn node ? { nodeId: node.id } : null;\n\t\t\t}\n\t\t\tcase 'direct': {\n\t\t\t\tconst handle = this.hitHandle(screen);\n\t\t\t\tif (handle) return { nodeId: handle.nodeId, handle };\n\t\t\t\tconst anchor = this.hitAnchor(screen, getVisibleAnchors(this.view()));\n\t\t\t\tif (anchor) return { nodeId: anchor.nodeId, anchor };\n\t\t\t\tconst widget = this.hitCornerWidget(screen);\n\t\t\t\tif (widget) return { nodeId: widget.ref.nodeId, corner: widget.ref };\n\t\t\t\tfor (const id of getEditablePathIds(this.view())) {\n\t\t\t\t\tconst node = this.getNode(id) as VectorPathNode;\n\t\t\t\t\tconst seg = nearestSegment(node, this.toLocal(id, p));\n\t\t\t\t\tif (seg && seg.distance <= this.tolerance()) {\n\t\t\t\t\t\tconst point = applyMatrix(this.worldOf(id), seg.point);\n\t\t\t\t\t\treturn { nodeId: id, segment: { nodeId: id, subpath: seg.subpath, segment: seg.segment, t: seg.t, point } };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst path = this.hitPath(p);\n\t\t\t\treturn path ? { nodeId: path.id } : null;\n\t\t\t}\n\t\t\tcase 'pen': {\n\t\t\t\tconst refs = this.pen ? [{ nodeId: this.pen.nodeId, subpath: this.pen.subpath, index: 0 }] : this.openEndpoints();\n\t\t\t\tconst anchor = this.hitAnchor(screen, refs);\n\t\t\t\treturn anchor ? { nodeId: anchor.nodeId, anchor } : null;\n\t\t\t}\n\t\t\tcase 'scissors': {\n\t\t\t\tconst seg = this.nearestOutline(p);\n\t\t\t\treturn seg ? { nodeId: seg.nodeId, segment: seg } : null;\n\t\t\t}\n\t\t\tcase 'blend': {\n\t\t\t\tconst hit = this.blendHit(screen, p);\n\t\t\t\tif (!hit) return null;\n\t\t\t\treturn hit.anchor === null ? { nodeId: hit.nodeId } : { nodeId: hit.nodeId, anchor: { nodeId: hit.nodeId, subpath: 0, index: hit.anchor } };\n\t\t\t}\n\t\t\tcase 'width': {\n\t\t\t\tconst hit = this.hitWidthHandle(screen);\n\t\t\t\tif (hit) return { nodeId: hit.ref.nodeId, widthPoint: hit.ref };\n\t\t\t\tconst spot = this.strokeSpotAt(p);\n\t\t\t\treturn spot ? { nodeId: spot.nodeId, strokeSpot: spot } : null;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate widthView(): Pick<VectorEditorState, 'document' | 'selection' | 'tool' | 'hover' | 'interaction' | 'selectedWidthPoints'> {\n\t\treturn { document: this.doc, selection: this.selection, tool: this.tool, hover: this.hover, interaction: this.interaction, selectedWidthPoints: this.widthPoints };\n\t}\n\n\t/** The width point centre or side end under `screen` (width tool). */\n\tprivate hitWidthHandle(screen: Vec2): { ref: VectorWidthPointRef & { part: 'center' | 'left' | 'right' } } | null {\n\t\tconst shown = this.hitWidthHandleIn(screen, this.widthView());\n\t\tif (shown) return shown;\n\t\t// The points of the stroke under the pointer, when no hover has shown them yet.\n\t\tconst spot = this.strokeSpotAt(this.screenToDocument(screen));\n\t\treturn spot && spot.nodeId !== this.hover?.nodeId ? this.hitWidthHandleIn(screen, { ...this.widthView(), hover: { nodeId: spot.nodeId } }) : null;\n\t}\n\n\tprivate hitWidthHandleIn(screen: Vec2, view: Pick<VectorEditorState, 'document' | 'selection' | 'tool' | 'hover' | 'interaction' | 'selectedWidthPoints'>): { ref: VectorWidthPointRef & { part: 'center' | 'left' | 'right' } } | null {\n\t\tlet best: { ref: VectorWidthPointRef & { part: 'center' | 'left' | 'right' } } | null = null;\n\t\tlet bestD = Infinity;\n\t\tfor (const h of getVectorWidthHandles(view)) {\n\t\t\tfor (const [part, q] of [['center', h], ['left', h.left], ['right', h.right]] as const) {\n\t\t\t\tconst s = this.documentToScreen(q);\n\t\t\t\tconst d = Math.hypot(s.x - screen.x, s.y - screen.y);\n\t\t\t\t// Ties go to the centre (listed first), so a zero-width point can still be moved.\n\t\t\t\tif (d <= this.hitRadius && d < bestD - 1e-9) {\n\t\t\t\t\tbest = { ref: { ...h.ref, part } };\n\t\t\t\t\tbestD = d;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\t/** The nearest stroked, unlocked visible path whose stroke is under document point `p` (width tool). */\n\tprivate strokeSpotAt(p: Vec2): VectorStrokeSpot | null {\n\t\tlet best: (VectorStrokeSpot & { distance: number }) | null = null;\n\t\tconst tol = this.tolerance();\n\t\twalkVectorNodes(this.doc.nodes, (n) => {\n\t\t\tif (n.type !== 'path' || n.visible === false || n.locked) return;\n\t\t\tif (hiddenOrLocked(this.doc.nodes, n.id)) return;\n\t\t\tconst g = vectorStrokeWidthGeometry(n);\n\t\t\tif (!g) return;\n\t\t\tconst m = this.worldOf(n.id);\n\t\t\tconst local = applyMatrix(invertMatrix(m), p);\n\t\t\tconst near = nearestStrokePosition(g.line, [local.x, local.y]);\n\t\t\tif (!near) return;\n\t\t\tconst profile = strokeWidthProfileOf(n);\n\t\t\tconst w = widthProfileAt(profile, near.t);\n\t\t\tconst reach = Math.max(near.side === 'left' ? w.left : w.right, 0) * g.unit + tol;\n\t\t\tif (near.distance > reach || near.distance > widthProfileMaxScale(profile) * g.unit + tol) return;\n\t\t\tif (best && near.distance >= best.distance) return;\n\t\t\tconst nx = near.tangent[1];\n\t\t\tconst ny = -near.tangent[0];\n\t\t\tconst [cx, cy] = near.point;\n\t\t\tbest = {\n\t\t\t\tnodeId: n.id,\n\t\t\t\tsubpath: near.contour,\n\t\t\t\tt: near.t,\n\t\t\t\tpoint: applyMatrix(m, { x: cx, y: cy }),\n\t\t\t\tleft: applyMatrix(m, { x: cx + nx * w.left * g.unit, y: cy + ny * w.left * g.unit }),\n\t\t\t\tright: applyMatrix(m, { x: cx - nx * w.right * g.unit, y: cy - ny * w.right * g.unit }),\n\t\t\t\tdistance: near.distance\n\t\t\t};\n\t\t});\n\t\tif (!best) return null;\n\t\tconst { distance: _d, ...spot } = best as VectorStrokeSpot & { distance: number };\n\t\treturn spot;\n\t}\n\n\tprivate downWidth(screen: Vec2, p: Vec2, input: VectorPointerInput) {\n\t\tconst hit = this.hitWidthHandle(screen);\n\t\tconst spot = hit ? null : this.strokeSpotAt(p);\n\t\tconst nodeId = hit?.ref.nodeId ?? spot?.nodeId;\n\t\tconst node = nodeId ? this.getNode(nodeId) : undefined;\n\t\tconst g = node?.type === 'path' ? vectorStrokeWidthGeometry(node) : null;\n\t\tif (this.widthEdit) this.widthEdit = null;\n\t\tif (!node || node.type !== 'path' || !g) {\n\t\t\t// A click away from strokes deselects (Shift keeps the selection).\n\t\t\tif (!input.shift) this.widthPoints = [];\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst before = this.snapshot();\n\t\tconst local = this.toLocal(node.id, p);\n\t\tlet profile: VectorWidthPoint[];\n\t\tlet ref: VectorWidthPointRef;\n\t\tlet mode: 'width' | 'move' | 'copy' = 'width';\n\t\tlet side: 'both' | 'left' | 'right' = 'both';\n\t\tlet group: number[] = [];\n\t\tlet shift = false;\n\t\tlet t: number;\n\t\tlet subpath: number;\n\t\tif (hit) {\n\t\t\tprofile = sanitizeWidthProfile(node.strokeWidthProfile);\n\t\t\tref = { nodeId: node.id, index: hit.ref.index, subpath: hit.ref.subpath };\n\t\t\tt = profile[ref.index]?.t ?? 0;\n\t\t\tsubpath = hit.ref.subpath;\n\t\t\tif (hit.ref.part === 'center') mode = input.alt && profile.length < VECTOR_WIDTH_PROFILE_MAX_POINTS ? 'copy' : 'move';\n\t\t\telse if (input.alt) side = hit.ref.part;\n\t\t\tconst same = this.widthPoints.length > 0 && this.widthPoints[0].nodeId === node.id;\n\t\t\tconst selected = same && this.widthPoints.some((r) => r.index === ref.index);\n\t\t\tif (input.shift && mode !== 'copy') {\n\t\t\t\t// Shift-click adds the point to the selection, or takes it out (and drags nothing).\n\t\t\t\tif (selected) {\n\t\t\t\t\tthis.widthPoints = this.widthPoints.filter((r) => r.index !== ref.index);\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.widthPoints = same ? [...this.widthPoints, ref] : [ref];\n\t\t\t\tshift = true;\n\t\t\t} else if (mode === 'copy' || !selected) this.widthPoints = [ref];\n\t\t\tif (mode !== 'copy') group = this.widthPoints.filter((r) => r.index !== ref.index).map((r) => r.index);\n\t\t} else {\n\t\t\tconst near = nearestStrokePosition(g.line, [local.x, local.y])!;\n\t\t\tconst inserted = insertWidthPoint(strokeWidthProfileOf(node), near.t);\n\t\t\tprofile = inserted.points;\n\t\t\tsubpath = near.contour;\n\t\t\tt = near.t;\n\t\t\tref = { nodeId: node.id, index: inserted.index, subpath };\n\t\t\tif (input.alt) side = near.side;\n\t\t\tthis.widthPoints = [ref];\n\t\t}\n\t\tconst at = strokePositionPoint(g.line, subpath, t);\n\t\tif (!at) return;\n\t\tconst base = { ...this.doc, nodes: mapTree(this.doc.nodes, (n) => (n.id === node.id ? { ...(n as VectorPathNode), strokeWidthProfile: profile } : n)) };\n\t\tthis.doc = base;\n\t\tthis.drag = {\n\t\t\tkind: 'width',\n\t\t\tref,\n\t\t\tmode,\n\t\t\tside,\n\t\t\tsource: ref.index,\n\t\t\tgroup,\n\t\t\tshift,\n\t\t\trenumber: null,\n\t\t\torigin: { x: at.point[0], y: at.point[1] },\n\t\t\tnormal: { x: at.tangent[1], y: -at.tangent[0] },\n\t\t\tunit: g.unit,\n\t\t\tprofile,\n\t\t\tbefore,\n\t\t\tbase,\n\t\t\tstart: p,\n\t\t\tmoved: false,\n\t\t\tcreated: !hit\n\t\t};\n\t\tconst w = profile[ref.index];\n\t\tthis.interaction = { type: 'width', ref, mode, width: (w.left + w.right) * g.unit };\n\t\tthis.notify(!hit);\n\t}\n\n\t/**\n\t * A width-tool slide to `t` on the dragged point's subpath lands exactly on another width\n\t * point (not `skip`) within the pick distance on screen: dropping a point onto another makes\n\t * a discontinuous point (two points at one `t`, the width steps there).\n\t */\n\tprivate snapWidthT(drag: Extract<Drag, { kind: 'width' }>, node: VectorPathNode, line: BezierPath, t: number, skip: number): number {\n\t\tconst m = this.worldOf(node.id, drag.base);\n\t\tconst at = strokePositionPoint(line, drag.ref.subpath, t);\n\t\tif (!at) return t;\n\t\tconst here = this.documentToScreen(applyMatrix(m, { x: at.point[0], y: at.point[1] }));\n\t\tlet best = t;\n\t\tlet bestD = this.hitRadius;\n\t\tdrag.profile.forEach((w, i) => {\n\t\t\tif (i === skip || drag.group.includes(i)) return;\n\t\t\tconst q = strokePositionPoint(line, drag.ref.subpath, w.t);\n\t\t\tif (!q) return;\n\t\t\tconst s = this.documentToScreen(applyMatrix(m, { x: q.point[0], y: q.point[1] }));\n\t\t\tconst d = Math.hypot(s.x - here.x, s.y - here.y);\n\t\t\tif (d <= bestD) {\n\t\t\t\tbest = w.t;\n\t\t\t\tbestD = d;\n\t\t\t}\n\t\t});\n\t\treturn best;\n\t}\n\n\t/** The nearest outline point of any visible, unlocked path within the pick distance of document point `p`. */\n\tprivate nearestOutline(p: Vec2): NonNullable<VectorHover['segment']> | null {\n\t\tlet best: (NonNullable<VectorHover['segment']> & { distance: number }) | null = null;\n\t\tconst tol = this.tolerance();\n\t\tconst visit = (list: readonly VectorNode[], parent: AffineMatrix) => {\n\t\t\tfor (let i = list.length - 1; i >= 0; i--) {\n\t\t\t\tconst n = list[i];\n\t\t\t\tif (n.visible === false || n.locked) continue;\n\t\t\t\tconst m = multiplyMatrices(nodeMatrix(n), parent);\n\t\t\t\tif (n.type === 'group') {\n\t\t\t\t\tvisit(n.children, m);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst hit = nearestSegment(n, applyMatrix(invertMatrix(m), p));\n\t\t\t\t// Node matrices are rigid, so local distances are document distances.\n\t\t\t\tif (hit && hit.distance <= tol && (!best || hit.distance < best.distance - 1e-9)) {\n\t\t\t\t\tbest = { nodeId: n.id, subpath: hit.subpath, segment: hit.segment, t: hit.t, point: applyMatrix(m, hit.point), distance: hit.distance };\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tvisit(this.doc.nodes, IDENTITY_MATRIX);\n\t\tif (!best) return null;\n\t\tconst { distance: _d, ...seg } = best as NonNullable<VectorHover['segment']> & { distance: number };\n\t\treturn seg;\n\t}\n\n\tprivate sameHover(a: VectorHover | null, b: VectorHover | null): boolean {\n\t\treturn JSON.stringify(a) === JSON.stringify(b);\n\t}\n\n\t/** Primary button down on the canvas at `screen` (pixels relative to the canvas). */\n\tpointerDown(screen: Vec2, input: VectorPointerInput = {}): void {\n\t\tthis.cancelDrag();\n\t\tif (input.button === 1 || this.spaceHeld) {\n\t\t\tthis.drag = { kind: 'pan', start: screen, viewport: this.viewport };\n\t\t\tthis.interaction = { type: 'pan' };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (input.button !== undefined && input.button !== 0) return;\n\t\tconst p = this.screenToDocument(screen);\n\t\tthis.pointer = p;\n\t\tswitch (this.tool) {\n\t\t\tcase 'select':\n\t\t\t\treturn this.downSelect(screen, p, input);\n\t\t\tcase 'direct':\n\t\t\t\treturn this.downDirect(screen, p, input);\n\t\t\tcase 'pen':\n\t\t\t\treturn this.downPen(screen, p, input);\n\t\t\tcase 'pencil':\n\t\t\t\treturn this.downPencil(p, input);\n\t\t\tcase 'width':\n\t\t\t\treturn this.downWidth(screen, p, input);\n\t\t\tcase 'shapeBuilder':\n\t\t\tcase 'paintBucket':\n\t\t\t\treturn this.downBuilder(p, input);\n\t\t\tcase 'blend':\n\t\t\t\treturn this.downBlend(screen, p);\n\t\t\tcase 'scissors': {\n\t\t\t\tconst seg = this.nearestOutline(p);\n\t\t\t\tif (seg) this.cutPathAt(seg.nodeId, seg.point);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'knife':\n\t\t\tcase 'eraser':\n\t\t\t\tthis.drag = { kind: 'stroke', tool: this.tool, points: [p], before: this.snapshot() };\n\t\t\t\tthis.interaction = { type: 'stroke', tool: this.tool, points: [p], width: this.tool === 'eraser' ? this.editing.eraserSize : 0 };\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\tdefault:\n\t\t\t\treturn this.downShape(p, input);\n\t\t}\n\t}\n\n\t/** The shape under the pointer for the Blend tool, and the anchor of its first subpath under it. */\n\tprivate blendHit(screen: Vec2, p: Vec2): VectorBlendStart | null {\n\t\tconst node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance());\n\t\tif (!node) return null;\n\t\tlet anchor: number | null = null;\n\t\tif (node.type === 'path' && node.subpaths[0]) {\n\t\t\tconst refs = node.subpaths[0].anchors.map((_, index) => ({ nodeId: node.id, subpath: 0, index }));\n\t\t\tanchor = this.hitAnchor(screen, refs)?.index ?? null;\n\t\t}\n\t\treturn { nodeId: node.id, anchor };\n\t}\n\n\t/** Blend tool: the first click picks the shape to blend from, the next one blends it into the shape clicked. */\n\tprivate downBlend(screen: Vec2, p: Vec2) {\n\t\tconst hit = this.blendHit(screen, p);\n\t\tconst start = this.blendStart;\n\t\tif (!hit) {\n\t\t\tif (start) {\n\t\t\t\tthis.blendStart = null;\n\t\t\t\tthis.notify();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (!start || start.nodeId === hit.nodeId) {\n\t\t\tthis.blendStart = hit;\n\t\t\tthis.selection = [hit.nodeId];\n\t\t\tthis.anchors = [];\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tthis.blendStart = null;\n\t\tconst first = this.getNode(start.nodeId);\n\t\tif (isVectorBlendGroup(first)) {\n\t\t\t// Clicking on from a blend adds the shape as its next key.\n\t\t\tif (!this.addBlendKey(first.id, hit.nodeId, { start: hit.anchor }).ok) this.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst starts: Record<string, number> = {};\n\t\tif (start.anchor !== null) starts[start.nodeId] = start.anchor;\n\t\tif (hit.anchor !== null) starts[hit.nodeId] = hit.anchor;\n\t\tif (!this.blend({ ids: [start.nodeId, hit.nodeId], keepOrder: true, starts })) this.notify();\n\t}\n\n\tprivate downSelect(screen: Vec2, p: Vec2, input: VectorPointerInput) {\n\t\tconst handle = this.hitTransformHandle(screen);\n\t\tif (handle) {\n\t\t\tconst frame = this.frameOf(this.selectedRoots())!;\n\t\t\tconst before = this.snapshot();\n\t\t\tconst center = applyMatrix(rotateMatrix(frame.angle), { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 });\n\t\t\tthis.drag =\n\t\t\t\thandle === 'rotate'\n\t\t\t\t\t? { kind: 'rotate', start: p, center, before, base: this.doc }\n\t\t\t\t\t: { kind: 'scale', handle, start: p, frame, before, base: this.doc };\n\t\t\tthis.interaction = handle === 'rotate' ? { type: 'rotate', angle: 0 } : { type: 'scale', handle, sx: 1, sy: 1 };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance(), { entered: this.enteredGroups() });\n\t\tif (node) {\n\t\t\tconst selected = this.selection.includes(node.id);\n\t\t\tif (input.shift) {\n\t\t\t\tthis.selection = selected ? this.selection.filter((id) => id !== node.id) : [...this.selection, node.id];\n\t\t\t\tif (selected) {\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else if (!selected) this.selection = [node.id];\n\t\t\tthis.anchors = [];\n\t\t\tconst before = this.snapshot();\n\t\t\tthis.drag = {\n\t\t\t\tkind: 'move',\n\t\t\t\tstart: p,\n\t\t\t\torigin: this.boundsOf(this.selectedRoots()),\n\t\t\t\tbefore,\n\t\t\t\tbase: this.doc,\n\t\t\t\tmoved: false,\n\t\t\t\tduplicated: false,\n\t\t\t\tclickId: selected && !input.shift ? node.id : null\n\t\t\t};\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (!input.shift) this.selection = [];\n\t\tthis.drag = { kind: 'marquee', start: p, base: this.selection, baseAnchors: [], anchorsMode: false };\n\t\tthis.interaction = { type: 'marquee', bounds: { x: p.x, y: p.y, width: 0, height: 0 } };\n\t\tthis.notify();\n\t}\n\n\tprivate downDirect(screen: Vec2, p: Vec2, input: VectorPointerInput) {\n\t\tconst handle = this.hitHandle(screen);\n\t\tif (handle) {\n\t\t\tthis.drag = { kind: 'handle', ref: handle, before: this.snapshot(), base: this.doc };\n\t\t\tthis.interaction = { type: 'handle', ref: handle };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst anchor = this.hitAnchor(screen, getVisibleAnchors(this.view()));\n\t\tif (anchor) {\n\t\t\tconst selected = this.anchors.some((a) => sameRef(a, anchor));\n\t\t\tthis.editingId = anchor.nodeId;\n\t\t\tif (input.alt) {\n\t\t\t\t// Alt-drag an anchor pulls out fresh symmetric handles.\n\t\t\t\tthis.anchors = [anchor];\n\t\t\t\tthis.drag = { kind: 'pull', ref: anchor, before: this.snapshot(), base: this.doc, label: 'Make Smooth', outOnly: false, closing: false, altBreaks: false };\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (input.shift) {\n\t\t\t\tthis.anchors = selected ? this.anchors.filter((a) => !sameRef(a, anchor)) : [...this.anchors, anchor];\n\t\t\t\tif (selected) {\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else if (!selected) this.anchors = [anchor];\n\t\t\tthis.drag = { kind: 'anchors', start: p, before: this.snapshot(), base: this.doc, moved: false, clickRef: selected && !input.shift ? anchor : null, lead: anchor };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst widget = this.hitCornerWidget(screen);\n\t\tif (widget) {\n\t\t\tconst refs = getVectorCornerWidgets(this.cornerView()).map((w) => w.ref);\n\t\t\tthis.drag = { kind: 'corner', widget, refs, start: p, before: this.snapshot(), base: this.doc, moved: false, alt: !!input.alt, shift: !!input.shift };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tfor (const id of getEditablePathIds(this.view())) {\n\t\t\tconst node = this.getNode(id) as VectorPathNode;\n\t\t\tconst seg = nearestSegment(node, this.toLocal(id, p));\n\t\t\tif (!seg || seg.distance > this.tolerance()) continue;\n\t\t\tconst sp = node.subpaths[seg.subpath];\n\t\t\tif (input.alt) {\n\t\t\t\tconst before = this.snapshot();\n\t\t\t\tconst { subpath, index } = insertAnchor(sp, seg.segment, seg.t);\n\t\t\t\tthis.replaceNode(id, { ...node, subpaths: node.subpaths.map((s, i) => (i === seg.subpath ? subpath : s)) });\n\t\t\t\tconst ref = { nodeId: id, subpath: seg.subpath, index };\n\t\t\t\tthis.anchors = [ref];\n\t\t\t\tthis.editingId = id;\n\t\t\t\tthis.drag = { kind: 'anchors', start: p, before, base: this.doc, moved: true, clickRef: null, lead: ref };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// A click selects the segment's anchors; a drag bends it through the pointer.\n\t\t\tconst a = { nodeId: id, subpath: seg.subpath, index: seg.segment };\n\t\t\tconst b = { nodeId: id, subpath: seg.subpath, index: (seg.segment + 1) % sp.anchors.length };\n\t\t\tthis.anchors = input.shift ? [...this.anchors.filter((r) => !sameRef(r, a) && !sameRef(r, b)), a, b] : [a, b];\n\t\t\tthis.editingId = id;\n\t\t\tthis.drag = { kind: 'bend', ref: { nodeId: id, subpath: seg.subpath, segment: seg.segment }, t: seg.t, start: p, before: this.snapshot(), base: this.doc, moved: false };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst path = this.hitPath(p);\n\t\tif (path) {\n\t\t\tthis.editingId = path.id;\n\t\t\tif (!input.shift) this.selection = [path.id];\n\t\t\telse if (!this.selection.includes(path.id)) this.selection = [...this.selection, path.id];\n\t\t\tthis.anchors = path.subpaths.flatMap((sp, s) => sp.anchors.map((_, i) => ({ nodeId: path.id, subpath: s, index: i })));\n\t\t\tthis.drag = { kind: 'anchors', start: p, before: this.snapshot(), base: this.doc, moved: false, clickRef: null, lead: null };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (!input.shift) {\n\t\t\tthis.anchors = [];\n\t\t}\n\t\tthis.drag = { kind: 'marquee', start: p, base: this.selection, baseAnchors: this.anchors, anchorsMode: getEditablePathIds(this.view()).length > 0 };\n\t\tthis.interaction = { type: 'marquee', bounds: { x: p.x, y: p.y, width: 0, height: 0 } };\n\t\tthis.notify();\n\t}\n\n\tprivate downPen(screen: Vec2, p: Vec2, input: VectorPointerInput) {\n\t\tconst before = this.snapshot();\n\t\tconst sp = this.pen ? (this.getNode(this.pen.nodeId) as VectorPathNode | undefined)?.subpaths[this.pen.subpath] : undefined;\n\t\tif (this.pen && sp) {\n\t\t\tconst pen = this.pen;\n\t\t\tconst first = { nodeId: pen.nodeId, subpath: pen.subpath, index: 0 };\n\t\t\tif (sp.anchors.length >= 2 && this.hitAnchor(screen, [first])) {\n\t\t\t\tconst node = this.getNode(pen.nodeId) as VectorPathNode;\n\t\t\t\tthis.replaceNode(pen.nodeId, { ...node, subpaths: node.subpaths.map((s, i) => (i === pen.subpath ? { ...s, closed: true } : s)) });\n\t\t\t\tthis.drag = { kind: 'pull', ref: first, before, base: this.doc, label: 'Close Path', outOnly: false, closing: true, altBreaks: true };\n\t\t\t\tthis.interaction = { type: 'pen', nodeId: pen.nodeId };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst last = getAnchorWorld(this.doc, { nodeId: pen.nodeId, subpath: pen.subpath, index: sp.anchors.length - 1 })!;\n\t\t\tconst leave = { at: { x: last.x, y: last.y }, constrain: input.shift ? ('angle' as const) : null };\n\t\t\tlet pt = this.snapDrawn(p, leave, input, () => this.drawingTargets(pen.nodeId));\n\t\t\tpt = this.toLocal(pen.nodeId, pt);\n\t\t\tconst node = this.getNode(pen.nodeId) as VectorPathNode;\n\t\t\tconst anchors = [...sp.anchors, { x: pt.x, y: pt.y }];\n\t\t\tthis.replaceNode(pen.nodeId, { ...node, subpaths: node.subpaths.map((s, i) => (i === pen.subpath ? { ...s, anchors } : s)) });\n\t\t\tconst ref = { nodeId: pen.nodeId, subpath: pen.subpath, index: anchors.length - 1 };\n\t\t\tthis.anchors = [ref];\n\t\t\tthis.drag = { kind: 'pull', ref, before, base: this.doc, label: 'Add Point', outOnly: false, closing: false, altBreaks: true };\n\t\t\tthis.interaction = { type: 'pen', nodeId: pen.nodeId };\n\t\t\tthis.notify(true);\n\t\t\treturn;\n\t\t}\n\t\tconst endpoint = this.hitAnchor(screen, this.openEndpoints());\n\t\tif (endpoint) {\n\t\t\tlet node = this.getNode(endpoint.nodeId) as VectorPathNode;\n\t\t\tlet index = endpoint.index;\n\t\t\tif (index === 0 && node.subpaths[endpoint.subpath].anchors.length > 1) {\n\t\t\t\tnode = { ...node, subpaths: node.subpaths.map((s, i) => (i === endpoint.subpath ? reverseSubpath(s) : s)) };\n\t\t\t\tthis.replaceNode(node.id, node);\n\t\t\t\tindex = node.subpaths[endpoint.subpath].anchors.length - 1;\n\t\t\t}\n\t\t\tthis.pen = { nodeId: node.id, subpath: endpoint.subpath };\n\t\t\tthis.selection = [node.id];\n\t\t\tthis.editingId = node.id;\n\t\t\tconst ref = { nodeId: node.id, subpath: endpoint.subpath, index };\n\t\t\tthis.anchors = [ref];\n\t\t\tthis.drag = { kind: 'pull', ref, before, base: this.doc, label: 'Continue Path', outOnly: true, closing: false, altBreaks: true };\n\t\t\tthis.interaction = { type: 'pen', nodeId: node.id };\n\t\t\tthis.notify(this.doc !== before.doc);\n\t\t\treturn;\n\t\t}\n\t\tconst pt = this.snapDrawn(p, null, input, () => this.drawingTargets(null));\n\t\tconst node: VectorPathNode = { id: this.newId(), type: 'path', name: 'Path', ...this.styleFor(true), subpaths: [{ anchors: [{ x: pt.x, y: pt.y }], closed: false }] };\n\t\tthis.setDoc([...this.doc.nodes, node]);\n\t\tthis.pen = { nodeId: node.id, subpath: 0 };\n\t\tthis.selection = [node.id];\n\t\tthis.editingId = node.id;\n\t\tconst ref = { nodeId: node.id, subpath: 0, index: 0 };\n\t\tthis.anchors = [ref];\n\t\tthis.drag = { kind: 'pull', ref, before, base: this.doc, label: 'Start Path', outOnly: false, closing: false, altBreaks: true };\n\t\tthis.interaction = { type: 'pen', nodeId: node.id };\n\t\tthis.notify(true);\n\t}\n\n\tprivate downPencil(p: Vec2, input: VectorPointerInput = {}) {\n\t\tconst before = this.snapshot();\n\t\tconst node: VectorPathNode = {\n\t\t\tid: this.newId(),\n\t\t\ttype: 'path',\n\t\t\tname: 'Pencil',\n\t\t\t...this.styleFor(true),\n\t\t\tstrokeLinecap: this.defaults.strokeLinecap ?? 'round',\n\t\t\tstrokeLinejoin: this.defaults.strokeLinejoin ?? 'round',\n\t\t\tsubpaths: [{ anchors: [{ x: p.x, y: p.y }], closed: false }]\n\t\t};\n\t\tthis.setDoc([...this.doc.nodes, node]);\n\t\tconst lazy = new LazyMouse((this.editing.pencilSmoothing * VECTOR_PENCIL_STRING_PX) / this.viewport.zoom);\n\t\tlazy.push([p.x, p.y]);\n\t\tconst mode = this.editing.pencilPressure;\n\t\tconst hasPressure = typeof input.pressure === 'number' && Number.isFinite(input.pressure);\n\t\tconst record = hasPressure && (mode === 'all' || (mode === 'pen' && input.pointerType === 'pen'));\n\t\tconst pressure = hasPressure ? input.pressure! : 0.5;\n\t\tthis.drag = { kind: 'pencil', nodeId: node.id, points: [[p.x, p.y]], before, lazy, pressures: record ? [pressure] : null, pressure };\n\t\tthis.interaction = { type: 'draw', tool: 'pencil', nodeId: node.id, bounds: { x: p.x, y: p.y, width: 0, height: 0 } };\n\t\tthis.notify(true);\n\t}\n\n\tprivate downShape(p: Vec2, input: VectorPointerInput) {\n\t\tconst before = this.snapshot();\n\t\t// The targets are gathered before the new shape joins the document, and kept for the drag.\n\t\tconst targets = this.drawingTargets(null);\n\t\tconst start = this.snapDrawn(p, null, input, () => targets);\n\t\tconst kind = this.tool as VectorShapeInput['kind'];\n\t\tconst node = this.createShapeNode({ kind, x: start.x, y: start.y, width: 0, height: 0 });\n\t\tthis.setDoc([...this.doc.nodes, node]);\n\t\tthis.drag = { kind: 'shape', tool: this.tool, start, nodeId: node.id, before, targets };\n\t\tthis.interaction = { type: 'draw', tool: this.tool, nodeId: node.id, bounds: { x: start.x, y: start.y, width: 0, height: 0 } };\n\t\tthis.notify(true);\n\t}\n\n\t/** Pointer moved over the canvas (with or without a button down). */\n\tpointerMove(screen: Vec2, input: VectorPointerInput = {}): void {\n\t\tconst p = this.screenToDocument(screen);\n\t\tconst drag = this.drag;\n\t\tif (!drag && (this.tool === 'shapeBuilder' || this.tool === 'paintBucket')) {\n\t\t\tthis.pointer = p;\n\t\t\tif (this.hoverBuilder(p, !!input.alt)) this.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (!drag) {\n\t\t\tconst hover = this.computeHover(screen);\n\t\t\tconst pointerMatters = this.tool === 'pen' && !!this.pen;\n\t\t\tconst guides = this.guides;\n\t\t\tthis.hover = hover;\n\t\t\tconst pointer = this.hoverSnap(p, input);\n\t\t\tif (this.sameHover(hover, this.state.hover) && !pointerMatters && JSON.stringify(guides) === JSON.stringify(this.guides)) return;\n\t\t\tthis.pointer = pointer;\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tthis.pointer = p;\n\t\tswitch (drag.kind) {\n\t\t\tcase 'pan':\n\t\t\t\tthis.viewport = { ...drag.viewport, x: drag.viewport.x + screen.x - drag.start.x, y: drag.viewport.y + screen.y - drag.start.y };\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\tcase 'move': {\n\t\t\t\tlet dx = p.x - drag.start.x;\n\t\t\t\tlet dy = p.y - drag.start.y;\n\t\t\t\tif (!drag.moved && Math.hypot(dx, dy) * this.viewport.zoom < 2) return;\n\t\t\t\tdrag.moved = true;\n\t\t\t\tif (input.shift) Math.abs(dx) > Math.abs(dy) ? (dy = 0) : (dx = 0);\n\t\t\t\tif (input.alt && !drag.duplicated) {\n\t\t\t\t\tdrag.duplicated = true;\n\t\t\t\t\tconst created: string[] = [];\n\t\t\t\t\tdrag.base = this.withDuplicates(drag.base, this.selectedRoots(), { x: 0, y: 0 }, created);\n\t\t\t\t\tthis.selection = created;\n\t\t\t\t\t// The originals stay behind as targets.\n\t\t\t\t\tdrag.targets = undefined;\n\t\t\t\t}\n\t\t\t\t({ dx, dy } = this.snapMove(drag, dx, dy, input));\n\t\t\t\tthis.doc = this.transformed(drag.base, new Set(this.selectedRoots()), translateMatrix(dx, dy));\n\t\t\t\tthis.interaction = { type: 'move', dx, dy };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'scale': {\n\t\t\t\t// Scale in the box's own (possibly rotated) frame.\n\t\t\t\tconst angle = drag.frame.angle;\n\t\t\t\tconst q = angle ? applyMatrix(rotateMatrix(-angle), p) : p;\n\t\t\t\tlet { sx, sy, origin } = scaleFor(drag.handle, drag.frame, q, input);\n\t\t\t\tif (this.snap.objects && !input.mod && drag.handle !== 'rotate') {\n\t\t\t\t\tdrag.targets ??= VectorSnapTargets.forResize(drag.base, this.selectedRoots());\n\t\t\t\t\tconst res = drag.targets.snapScale(drag.frame, drag.handle, sx, sy, input, this.snapTolerance());\n\t\t\t\t\t({ sx, sy } = res);\n\t\t\t\t\tthis.setGuides(res);\n\t\t\t\t} else this.setGuides(null);\n\t\t\t\tconst ids = new Set(this.selectedRoots());\n\t\t\t\tthis.doc = this.transformed(drag.base, ids, this.inFrame(angle, scaleMatrix(sx, sy, origin)));\n\t\t\t\tthis.interaction = { type: 'scale', handle: drag.handle, sx, sy };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'rotate': {\n\t\t\t\tconst a0 = Math.atan2(drag.start.y - drag.center.y, drag.start.x - drag.center.x);\n\t\t\t\tconst a1 = Math.atan2(p.y - drag.center.y, p.x - drag.center.x);\n\t\t\t\tlet angle = ((a1 - a0) * 180) / Math.PI;\n\t\t\t\tif (input.shift) angle = Math.round(angle / 15) * 15;\n\t\t\t\tthis.doc = this.transformed(drag.base, new Set(this.selectedRoots()), rotateMatrix(angle, drag.center));\n\t\t\t\tthis.interaction = { type: 'rotate', angle };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'marquee': {\n\t\t\t\tconst bounds = boundsFromPoints(drag.start, p);\n\t\t\t\tthis.interaction = { type: 'marquee', bounds };\n\t\t\t\tif (drag.anchorsMode) {\n\t\t\t\t\tconst inside = getVisibleAnchors(this.view()).filter((r) => {\n\t\t\t\t\t\tconst a = getAnchorWorld(this.doc, r)!;\n\t\t\t\t\t\treturn a.x >= bounds.x && a.x <= bounds.x + bounds.width && a.y >= bounds.y && a.y <= bounds.y + bounds.height;\n\t\t\t\t\t});\n\t\t\t\t\tthis.anchors = [...drag.baseAnchors, ...inside.filter((r) => !drag.baseAnchors.some((b) => sameRef(b, r)))];\n\t\t\t\t} else {\n\t\t\t\t\tconst hits = this.doc.nodes\n\t\t\t\t\t\t.filter((n) => !n.locked && n.visible !== false)\n\t\t\t\t\t\t.filter((n) => {\n\t\t\t\t\t\t\tconst b = vectorNodeBounds(n);\n\t\t\t\t\t\t\treturn b && boundsIntersect(b, bounds);\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.map((n) => n.id);\n\t\t\t\t\tthis.selection = [...new Set([...drag.base, ...hits])];\n\t\t\t\t}\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'anchors': {\n\t\t\t\tlet dx = p.x - drag.start.x;\n\t\t\t\tlet dy = p.y - drag.start.y;\n\t\t\t\tif (!drag.moved && Math.hypot(dx, dy) * this.viewport.zoom < 2) return;\n\t\t\t\tdrag.moved = true;\n\t\t\t\tconst lead = this.leadAnchor(drag);\n\t\t\t\tif (lead) {\n\t\t\t\t\t// The grabbed (or nearest) anchor snaps; the others move with it.\n\t\t\t\t\tconst leave = input.shift ? { at: lead, constrain: 'axis' as const, curves: false } : null;\n\t\t\t\t\tconst q = this.snapDrawn({ x: lead.x + dx, y: lead.y + dy }, leave, input, () => (drag.targets ??= VectorSnapTargets.forAnchorDrag(drag.base, this.anchors)));\n\t\t\t\t\tdx = q.x - lead.x;\n\t\t\t\t\tdy = q.y - lead.y;\n\t\t\t\t} else if (input.shift) Math.abs(dx) > Math.abs(dy) ? (dy = 0) : (dx = 0);\n\t\t\t\tconst single = this.anchors.length === 1 ? this.anchors[0] : null;\n\t\t\t\tconst from = single ? getAnchorWorld(drag.base, single) : undefined;\n\t\t\t\t// A lone point bends its segments with the Curvature setting (Alt held flips it).\n\t\t\t\tif (single && from && this.editing.curvatureMoves !== !!input.alt) this.doc = this.curvatureMoved(drag.base, single, { x: from.x + dx, y: from.y + dy });\n\t\t\t\telse this.doc = this.movedAnchors(drag.base, this.anchors, dx, dy);\n\t\t\t\tthis.interaction = { type: 'anchors', dx, dy };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'handle': {\n\t\t\t\tconst anchor = getAnchorWorld(drag.base, drag.ref);\n\t\t\t\tconst leave = anchor ? { at: { x: anchor.x, y: anchor.y }, constrain: input.shift ? ('angle' as const) : null } : null;\n\t\t\t\tconst pt = this.snapDrawn(p, leave, input, () => (drag.targets ??= VectorSnapTargets.forHandle(drag.base, drag.ref.nodeId)));\n\t\t\t\tconst local = this.toLocal(drag.ref.nodeId, pt, drag.base);\n\t\t\t\tthis.doc = this.updateAnchors(drag.base, [drag.ref], (a) => moveAnchorHandle(a, drag.ref.handle, local, input.alt));\n\t\t\t\tthis.interaction = { type: 'handle', ref: drag.ref };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'corner': {\n\t\t\t\tif (!drag.moved && Math.hypot(p.x - drag.start.x, p.y - drag.start.y) * this.viewport.zoom < 3) return;\n\t\t\t\tdrag.moved = true;\n\t\t\t\tconst c = drag.widget.corner;\n\t\t\t\tconst inv = invertMatrix(this.worldOf(drag.widget.ref.nodeId, drag.base));\n\t\t\t\tconst d = applyMatrixLinear(inv, { x: p.x - drag.start.x, y: p.y - drag.start.y });\n\t\t\t\tconst radius = Math.max(0, Math.min(c.maxRadius, drag.widget.drawnRadius + cornerRadiusChange(c, d)));\n\t\t\t\tthis.doc = this.updateAnchors(drag.base, drag.refs, (a) => setAnchorCorner(a, radius));\n\t\t\t\tthis.interaction = { type: 'corner', ref: drag.widget.ref, radius };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'bend': {\n\t\t\t\tif (!drag.moved && Math.hypot(p.x - drag.start.x, p.y - drag.start.y) * this.viewport.zoom < 2) return;\n\t\t\t\tdrag.moved = true;\n\t\t\t\tthis.doc = this.bent(drag.base, drag.ref, drag.t, p);\n\t\t\t\tthis.interaction = { type: 'bend', ref: drag.ref };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'builder':\n\t\t\t\treturn this.moveBuilder(drag, p);\n\t\t\tcase 'stroke': {\n\t\t\t\tconst last = drag.points[drag.points.length - 1];\n\t\t\t\tif (Math.hypot(p.x - last.x, p.y - last.y) * this.viewport.zoom < 1.5) return;\n\t\t\t\tdrag.points.push(p);\n\t\t\t\tthis.interaction = { type: 'stroke', tool: drag.tool, points: drag.points.slice(), width: drag.tool === 'eraser' ? this.editing.eraserSize : 0 };\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'pull': {\n\t\t\t\tconst anchor = getAnchorWorld(drag.base, drag.ref);\n\t\t\t\tif (!anchor) return;\n\t\t\t\tif (Math.hypot(p.x - anchor.x, p.y - anchor.y) * this.viewport.zoom < 2) {\n\t\t\t\t\tthis.doc = drag.base;\n\t\t\t\t\tthis.notify(true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.guides = null;\n\t\t\t\tlet pt = p;\n\t\t\t\tif (input.shift) pt = constrain45(anchor, pt);\n\t\t\t\tconst side = drag.closing ? 'in' : 'out';\n\t\t\t\t// Dragging out of the closing anchor pulls its incoming handle toward the pointer's mirror.\n\t\t\t\tconst pos = this.toLocal(drag.ref.nodeId, drag.closing ? { x: 2 * anchor.x - pt.x, y: 2 * anchor.y - pt.y } : pt, drag.base);\n\t\t\t\tthis.doc = this.updateAnchors(drag.base, [drag.ref], (a) => {\n\t\t\t\t\tif (drag.outOnly || (drag.altBreaks && input.alt)) return moveAnchorHandle(a, side, pos, true);\n\t\t\t\t\treturn moveAnchorHandle({ ...a, type: 'symmetric' }, side, pos);\n\t\t\t\t});\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'shape': {\n\t\t\t\tconst node = this.getNode(drag.nodeId);\n\t\t\t\tif (node?.type !== 'path') return;\n\t\t\t\tconst leave = { at: drag.start, constrain: input.shift ? (drag.tool === 'line' ? ('angle' as const) : ('square' as const)) : null, curves: false };\n\t\t\t\tlet end = this.snapDrawn(p, leave, input, () => (drag.targets ??= this.drawingTargets(null)));\n\t\t\t\tif (!this.snappedToObject && drag.tool !== 'line' && !input.shift && !input.mod && this.snap.objects && drag.targets) end = this.matchShapeSize(drag.targets, drag.start, end, input.alt);\n\t\t\t\tlet start = drag.start;\n\t\t\t\tlet bounds: VectorBounds;\n\t\t\t\tif (drag.tool === 'line') {\n\t\t\t\t\tif (input.shift) end = constrain45(start, end);\n\t\t\t\t\tif (input.alt) start = { x: 2 * start.x - end.x, y: 2 * start.y - end.y };\n\t\t\t\t\tbounds = { x: start.x, y: start.y, width: end.x - start.x, height: end.y - start.y };\n\t\t\t\t} else {\n\t\t\t\t\tlet w = end.x - start.x;\n\t\t\t\t\tlet h = end.y - start.y;\n\t\t\t\t\tif (input.shift) {\n\t\t\t\t\t\tconst s = Math.max(Math.abs(w), Math.abs(h));\n\t\t\t\t\t\tw = Math.sign(w || 1) * s;\n\t\t\t\t\t\th = Math.sign(h || 1) * s;\n\t\t\t\t\t}\n\t\t\t\t\tif (input.alt) start = { x: start.x - w, y: start.y - h };\n\t\t\t\t\tconst x2 = drag.start.x + w;\n\t\t\t\t\tconst y2 = drag.start.y + h;\n\t\t\t\t\tbounds = boundsFromPoints(start, { x: x2, y: y2 });\n\t\t\t\t}\n\t\t\t\tconst shaped = this.createShapeNode({ kind: drag.tool as VectorShapeInput['kind'], ...bounds });\n\t\t\t\tthis.replaceNode(drag.nodeId, { ...node, subpaths: shaped.subpaths });\n\t\t\t\tthis.interaction = { type: 'draw', tool: drag.tool, nodeId: drag.nodeId, bounds };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'width': {\n\t\t\t\tif (!drag.moved && Math.hypot(p.x - drag.start.x, p.y - drag.start.y) * this.viewport.zoom < 2) return;\n\t\t\t\tdrag.moved = true;\n\t\t\t\tconst node = findVectorNode(drag.base.nodes, drag.ref.nodeId) as VectorPathNode;\n\t\t\t\tconst local = this.toLocal(node.id, p, drag.base);\n\t\t\t\tconst src = drag.profile[drag.source];\n\t\t\t\tlet profile: VectorWidthPoint[];\n\t\t\t\tlet index = drag.source;\n\t\t\t\tif (drag.mode !== 'width') {\n\t\t\t\t\tconst g = vectorStrokeWidthGeometry(node);\n\t\t\t\t\tconst near = g ? nearestStrokePosition(g.line, [local.x, local.y], drag.ref.subpath) : null;\n\t\t\t\t\tif (!g || !near) return;\n\t\t\t\t\tconst t = drag.group.length ? near.t : this.snapWidthT(drag, node, g.line, near.t, drag.source);\n\t\t\t\t\tif (drag.mode === 'copy') {\n\t\t\t\t\t\t// The source stays; a copy of it slides (and may pass other points).\n\t\t\t\t\t\t({ points: profile, index } = insertWidthPoint(drag.profile, t, { left: src.left, right: src.right }, { step: true }));\n\t\t\t\t\t} else if (drag.group.length) {\n\t\t\t\t\t\tconst shifted = shiftWidthPoints(drag.profile, [drag.source, ...drag.group], t - src.t);\n\t\t\t\t\t\tprofile = shifted.points;\n\t\t\t\t\t\tdrag.renumber = shifted.renumber;\n\t\t\t\t\t\tindex = shifted.renumber.get(drag.source) ?? drag.source;\n\t\t\t\t\t} else profile = setWidthPoint(drag.profile, drag.source, { t });\n\t\t\t\t} else {\n\t\t\t\t\tconst off = ((local.x - drag.origin.x) * drag.normal.x + (local.y - drag.origin.y) * drag.normal.y) / drag.unit;\n\t\t\t\t\tconst patch =\n\t\t\t\t\t\tdrag.side === 'both' ? { left: Math.abs(off), right: Math.abs(off) } : drag.side === 'left' ? { left: Math.max(0, off) } : { right: Math.max(0, -off) };\n\t\t\t\t\tprofile = setWidthPoint(drag.profile, drag.source, patch);\n\t\t\t\t\tif (drag.group.length) {\n\t\t\t\t\t\t// Every selected point widens by as much as the dragged one.\n\t\t\t\t\t\tconst dl = profile[drag.source].left - src.left;\n\t\t\t\t\t\tconst dr = profile[drag.source].right - src.right;\n\t\t\t\t\t\tfor (const i of drag.group) profile = setWidthPoint(profile, i, { left: Math.max(0, drag.profile[i].left + dl), right: Math.max(0, drag.profile[i].right + dr) });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.doc = { ...drag.base, nodes: mapTree(drag.base.nodes, (n) => (n.id === node.id ? { ...(n as VectorPathNode), strokeWidthProfile: profile } : n)) };\n\t\t\t\tdrag.ref = { ...drag.ref, index };\n\t\t\t\tconst w = profile[index];\n\t\t\t\tthis.interaction = { type: 'width', ref: drag.ref, mode: drag.mode, width: (w.left + w.right) * drag.unit };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'pencil': {\n\t\t\t\tconst last = drag.points[drag.points.length - 1];\n\t\t\t\tlet moved = false;\n\t\t\t\tif (typeof input.pressure === 'number' && Number.isFinite(input.pressure)) drag.pressure = input.pressure;\n\t\t\t\tfor (const q of drag.lazy.push([p.x, p.y])) {\n\t\t\t\t\tif (Math.hypot(q[0] - last[0], q[1] - last[1]) * this.viewport.zoom < 1.5) continue;\n\t\t\t\t\tdrag.points.push(q);\n\t\t\t\t\tdrag.pressures?.push(drag.pressure);\n\t\t\t\t\tmoved = true;\n\t\t\t\t}\n\t\t\t\tconst string = drag.lazy.string();\n\t\t\t\tconst node = this.getNode(drag.nodeId);\n\t\t\t\tif (node?.type !== 'path') return;\n\t\t\t\tif (!moved) {\n\t\t\t\t\tif (string && this.interaction?.type === 'draw') {\n\t\t\t\t\t\tthis.interaction = { ...this.interaction, string: [{ x: string[0][0], y: string[0][1] }, { x: string[1][0], y: string[1][1] }] };\n\t\t\t\t\t\tthis.notify();\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.replaceNode(drag.nodeId, { ...node, subpaths: [{ anchors: drag.points.map(([x, y]) => ({ x, y })), closed: false }] });\n\t\t\t\tconst xs = drag.points.map((q) => q[0]);\n\t\t\t\tconst ys = drag.points.map((q) => q[1]);\n\t\t\t\tthis.interaction = {\n\t\t\t\t\ttype: 'draw',\n\t\t\t\t\ttool: 'pencil',\n\t\t\t\t\tnodeId: drag.nodeId,\n\t\t\t\t\tbounds: { x: Math.min(...xs), y: Math.min(...ys), width: Math.max(...xs) - Math.min(...xs), height: Math.max(...ys) - Math.min(...ys) },\n\t\t\t\t\t...(string ? { string: [{ x: string[0][0], y: string[0][1] }, { x: string[1][0], y: string[1][1] }] as [Vec2, Vec2] } : {})\n\t\t\t\t};\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Snaps a move of the selection by (dx, dy): its bounds line up with targets or space evenly, else go to the grid. */\n\tprivate snapMove(drag: Extract<Drag, { kind: 'move' }>, dx: number, dy: number, input: VectorModifiers): { dx: number; dy: number } {\n\t\tconst origin = drag.origin;\n\t\tif (input.mod || !origin) {\n\t\t\tthis.setGuides(null);\n\t\t\treturn { dx, dy };\n\t\t}\n\t\t// A Shift-locked axis stays put.\n\t\tconst axes = { x: !input.shift || dx !== 0, y: !input.shift || dy !== 0 };\n\t\tlet res = null;\n\t\tif (this.snap.objects) {\n\t\t\tdrag.targets ??= VectorSnapTargets.forMove(drag.base, this.selectedRoots());\n\t\t\tres = drag.targets.snapRect({ ...origin, x: origin.x + dx, y: origin.y + dy }, this.snapTolerance(), { spacing: true, axes });\n\t\t}\n\t\tthis.setGuides(res);\n\t\treturn {\n\t\t\tdx: res?.x ? dx + res.dx : axes.x ? this.gridValue(origin.x + dx) - origin.x : dx,\n\t\t\tdy: res?.y ? dy + res.dy : axes.y ? this.gridValue(origin.y + dy) - origin.y : dy\n\t\t};\n\t}\n\n\t/** The anchor a point drag snaps by, where it started: the grabbed one, else the moving one nearest the press. */\n\tprivate leadAnchor(drag: Extract<Drag, { kind: 'anchors' }>): Vec2 | null {\n\t\tconst ref = drag.lead && this.anchors.some((a) => sameRef(a, drag.lead!)) ? drag.lead : null;\n\t\tif (ref) {\n\t\t\tconst a = getAnchorWorld(drag.base, ref);\n\t\t\treturn a ? { x: a.x, y: a.y } : null;\n\t\t}\n\t\tlet best: Vec2 | null = null;\n\t\tfor (const r of this.anchors) {\n\t\t\tconst a = getAnchorWorld(drag.base, r);\n\t\t\tif (a && (!best || Math.hypot(a.x - drag.start.x, a.y - drag.start.y) < Math.hypot(best.x - drag.start.x, best.y - drag.start.y))) best = { x: a.x, y: a.y };\n\t\t}\n\t\treturn best;\n\t}\n\n\t/** A shape's dragged corner moved so its width or height matches another object's, when one is within reach. */\n\tprivate matchShapeSize(targets: VectorSnapTargets, start: Vec2, end: Vec2, fromCentre = false): Vec2 {\n\t\t// Dragged from the centre, the shape is twice as large as the drag.\n\t\tconst k = fromCentre ? 2 : 1;\n\t\tconst tol = this.snapTolerance() * k;\n\t\tconst out = { ...end };\n\t\tconst guides: VectorSnapGuides = { lines: [], labels: [] };\n\t\tfor (const axis of ['x', 'y'] as const) {\n\t\t\tconst d = end[axis] - start[axis];\n\t\t\tconst len = Math.abs(d) > 1e-9 ? targets.matchSize(Math.abs(d) * k, tol) : null;\n\t\t\tif (len === null || len <= 0) continue;\n\t\t\tout[axis] = start[axis] + (Math.sign(d) * len) / k;\n\t\t\tconst from = { ...out, [axis]: fromCentre ? 2 * start[axis] - out[axis] : start[axis] };\n\t\t\tguides.lines.push({ kind: 'size', x1: from.x, y1: from.y, x2: out.x, y2: out.y });\n\t\t\tguides.labels.push({ kind: 'size', x: (from.x + out.x) / 2, y: (from.y + out.y) / 2, text: `${axis === 'x' ? 'W' : 'H'} ${Math.round(len * 100) / 100}` });\n\t\t}\n\t\tthis.setGuides(guides);\n\t\treturn out;\n\t}\n\n\t/** Primary button released. */\n\tpointerUp(screen?: Vec2, input: VectorPointerInput = {}): void {\n\t\t// A pen reports no pressure as it lifts: keep the last pressure for the final move.\n\t\tif (screen && this.drag && this.drag.kind !== 'pan') this.pointerMove(screen, { ...input, pressure: undefined });\n\t\tconst drag = this.drag;\n\t\tthis.drag = null;\n\t\tthis.interaction = null;\n\t\tthis.guides = null;\n\t\tif (!drag) return;\n\t\tswitch (drag.kind) {\n\t\t\tcase 'pan':\n\t\t\tcase 'marquee':\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\tcase 'move':\n\t\t\t\tif (!drag.moved) {\n\t\t\t\t\tif (drag.clickId) this.selection = [drag.clickId];\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn this.commit(drag.duplicated ? 'Duplicate' : 'Move', drag.before);\n\t\t\tcase 'scale':\n\t\t\t\treturn this.commit('Scale', drag.before);\n\t\t\tcase 'rotate':\n\t\t\t\treturn this.commit('Rotate', drag.before);\n\t\t\tcase 'anchors':\n\t\t\t\tif (!drag.moved) {\n\t\t\t\t\tif (drag.clickRef) this.anchors = [drag.clickRef];\n\t\t\t\t\tif (drag.before.doc !== this.doc) return this.commit('Add Point', drag.before);\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn this.commit(drag.before.doc !== drag.base ? 'Add Point' : 'Move Points', drag.before);\n\t\t\tcase 'handle':\n\t\t\t\treturn this.commit('Move Handle', drag.before);\n\t\t\tcase 'bend':\n\t\t\t\tif (!drag.moved) {\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn this.commit('Bend Segment', drag.before);\n\t\t\tcase 'corner': {\n\t\t\t\tif (drag.moved) return this.commit('Corner Radius', drag.before);\n\t\t\t\tif (drag.alt) {\n\t\t\t\t\tconst kind = nextVectorCornerKind(drag.widget.kind);\n\t\t\t\t\tthis.doc = this.updateAnchors(drag.base, drag.refs, (a) => setAnchorCorner(a, a.corner?.radius ?? 0, kind));\n\t\t\t\t\treturn this.commit('Corner Kind', drag.before);\n\t\t\t\t}\n\t\t\t\t// A click selects the widget's corner, so the next drag rounds that corner alone.\n\t\t\t\tconst ref = drag.widget.ref;\n\t\t\t\tconst has = this.anchors.some((a) => sameRef(a, ref));\n\t\t\t\tthis.editingId = ref.nodeId;\n\t\t\t\tthis.anchors = drag.shift ? (has ? this.anchors.filter((a) => !sameRef(a, ref)) : [...this.anchors, ref]) : [ref];\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'width': {\n\t\t\t\tif (drag.moved) {\n\t\t\t\t\tconst several = drag.group.length > 0;\n\t\t\t\t\tif (drag.mode === 'copy') this.widthPoints = [drag.ref];\n\t\t\t\t\telse if (drag.mode === 'move' && several) {\n\t\t\t\t\t\t// Points moved together stay selected (re-sorting may renumber them).\n\t\t\t\t\t\tthis.widthPoints = [drag.source, ...drag.group].map((i) => ({ ...drag.ref, index: drag.renumber?.get(i) ?? i }));\n\t\t\t\t\t} else if (!several) this.widthPoints = [drag.ref];\n\t\t\t\t\tconst label = drag.mode === 'copy' ? 'Copy Width Point' : drag.mode === 'move' ? (several ? 'Move Width Points' : 'Move Width Point') : several ? 'Width Points' : 'Width Point';\n\t\t\t\t\treturn this.commit(label, drag.before);\n\t\t\t\t}\n\t\t\t\t// A click adds nothing; on a width point it selects it (just it, among several selected).\n\t\t\t\tif (drag.created) {\n\t\t\t\t\tthis.doc = drag.before.doc;\n\t\t\t\t\tthis.widthPoints = [];\n\t\t\t\t\tthis.notify(true);\n\t\t\t\t} else {\n\t\t\t\t\tif (!drag.shift && drag.group.length) this.widthPoints = [drag.ref];\n\t\t\t\t\tthis.notify();\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'builder':\n\t\t\t\treturn this.upBuilder(drag);\n\t\t\tcase 'stroke': {\n\t\t\t\tthis.notify();\n\t\t\t\tif (drag.tool === 'knife') this.knife(drag.points);\n\t\t\t\telse this.erase(drag.points);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'pull':\n\t\t\t\tif (drag.closing) this.pen = null;\n\t\t\t\treturn this.commit(drag.label, drag.before);\n\t\t\tcase 'shape': {\n\t\t\t\tconst node = this.getNode(drag.nodeId);\n\t\t\t\tconst b = node ? vectorNodeBounds(node) : null;\n\t\t\t\tconst min = 2 / this.viewport.zoom;\n\t\t\t\tif (!b || (b.width < min && b.height < min)) {\n\t\t\t\t\tthis.doc = drag.before.doc;\n\t\t\t\t\tthis.notify(true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.selection = [drag.nodeId];\n\t\t\t\tthis.anchors = [];\n\t\t\t\tthis.tool = 'select';\n\t\t\t\treturn this.commit(`Draw ${TOOL_LABELS[drag.tool]}`, drag.before);\n\t\t\t}\n\t\t\tcase 'pencil': {\n\t\t\t\tconst node = this.getNode(drag.nodeId);\n\t\t\t\tif (node?.type !== 'path' || drag.points.length < 2) {\n\t\t\t\t\tthis.doc = drag.before.doc;\n\t\t\t\t\tthis.notify(true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor (const q of drag.lazy.finish()) {\n\t\t\t\t\tdrag.points.push(q);\n\t\t\t\t\tdrag.pressures?.push(drag.pressure);\n\t\t\t\t}\n\t\t\t\tconst shape = this.editing.recognizeShapes ? recognizeInkShape(drag.points, { minSize: 6 / this.viewport.zoom }) : null;\n\t\t\t\tif (shape) {\n\t\t\t\t\tthis.replaceNode(drag.nodeId, { ...node, name: inkShapeLabel(shape), subpaths: [inkShapeSubpath(shape)] });\n\t\t\t\t\tthis.selection = [drag.nodeId];\n\t\t\t\t\treturn this.commit(`Draw ${inkShapeLabel(shape)}`, drag.before);\n\t\t\t\t}\n\t\t\t\tconst fitted = fitCubics(drag.points, 1.5 / this.viewport.zoom, { cornerAngleDeg: 60 });\n\t\t\t\tconst anchors: VectorAnchor[] = [];\n\t\t\t\tfor (const c of fitted) {\n\t\t\t\t\tif (!anchors.length) anchors.push({ x: c.p0[0], y: c.p0[1] });\n\t\t\t\t\tanchors[anchors.length - 1].out = { x: c.p1[0], y: c.p1[1] };\n\t\t\t\t\tanchors.push({ x: c.p3[0], y: c.p3[1], in: { x: c.p2[0], y: c.p2[1] } });\n\t\t\t\t}\n\t\t\t\tconst subpaths = anchors.length > 1 ? bezierToSubpaths(subpathsToBezier([{ anchors, closed: false }])) : node.subpaths;\n\t\t\t\tconst profile = drag.pressures ? pencilPressureProfile(drag.points, drag.pressures) : [];\n\t\t\t\tthis.replaceNode(drag.nodeId, { ...node, subpaths, ...(profile.length ? { strokeWidthProfile: profile } : {}) });\n\t\t\t\tthis.selection = [drag.nodeId];\n\t\t\t\treturn this.commit('Draw', drag.before);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** The pointer left the canvas. */\n\tpointerLeave(): void {\n\t\tif (this.drag) return;\n\t\tif (!this.hover && !this.pointer && !this.guides) return;\n\t\tthis.hover = null;\n\t\tthis.pointer = null;\n\t\tthis.guides = null;\n\t\tthis.notify();\n\t}\n\n\t/** Abandons the drag in progress (Escape, lost capture). */\n\tcancelDrag(): void {\n\t\tconst drag = this.drag;\n\t\tif (!drag) return;\n\t\tthis.drag = null;\n\t\tthis.interaction = null;\n\t\tthis.guides = null;\n\t\tif (drag.kind === 'pan' || drag.kind === 'builder') {\n\t\t\tif (drag.kind === 'builder') this.builderPreview = null;\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (drag.kind === 'marquee') {\n\t\t\tthis.selection = drag.base;\n\t\t\tthis.anchors = drag.baseAnchors;\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tthis.restore(drag.before);\n\t}\n\n\t/**\n\t * Double click: select tool enters path editing, direct tool toggles an anchor or adds one,\n\t * width tool opens the width-point editor (`widthPointEdit`).\n\t */\n\tdoubleClick(screen: Vec2): void {\n\t\tconst p = this.screenToDocument(screen);\n\t\tif (this.tool === 'select') {\n\t\t\tconst path = this.hitPath(p);\n\t\t\tif (!path) return;\n\t\t\tthis.tool = 'direct';\n\t\t\tthis.selection = [path.id];\n\t\t\tthis.editingId = path.id;\n\t\t\tthis.anchors = [];\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (this.tool === 'direct') {\n\t\t\tconst anchor = this.hitAnchor(screen, getVisibleAnchors(this.view()));\n\t\t\tif (anchor) {\n\t\t\t\tconst a = getAnchor(this.doc, anchor)!;\n\t\t\t\tthis.convertAnchors(a.in || a.out ? 'corner' : 'smooth', [anchor]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst id = this.hover?.segment?.nodeId ?? this.editingId;\n\t\t\tif (id) {\n\t\t\t\tconst node = this.getNode(id) as VectorPathNode | undefined;\n\t\t\t\tconst seg = node ? nearestSegment(node, this.toLocal(id, p)) : null;\n\t\t\t\tif (seg && seg.distance <= this.tolerance()) this.insertAnchorAt(id, p);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (this.tool === 'width') {\n\t\t\t// Width Point Edit: on a width point, or on a stroke (applying it adds a point there).\n\t\t\tconst hit = this.hitWidthHandle(screen);\n\t\t\tif (hit) {\n\t\t\t\tthis.openWidthPointEdit(hit.ref);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst spot = this.strokeSpotAt(p);\n\t\t\tif (spot) this.openWidthPointEdit({ nodeId: spot.nodeId, subpath: spot.subpath, t: spot.t });\n\t\t\treturn;\n\t\t}\n\t\tif (this.tool === 'pen') this.finishPath();\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Keyboard input\n\t// -------------------------------------------------------------------------\n\n\t/**\n\t * Handles editor shortcuts. Returns true when the key was used (call `preventDefault`).\n\t * Do not forward keys typed into text inputs.\n\t */\n\tkeyDown(input: VectorKeyInput): boolean {\n\t\tconst key = input.key;\n\t\tconst lower = key.length === 1 ? key.toLowerCase() : key;\n\t\tif (key === ' ') {\n\t\t\tif (!this.spaceHeld) {\n\t\t\t\tthis.spaceHeld = true;\n\t\t\t\tthis.notify();\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tif (input.mod) {\n\t\t\tswitch (lower) {\n\t\t\t\tcase 'z':\n\t\t\t\t\tinput.shift ? this.redo() : this.undo();\n\t\t\t\t\treturn true;\n\t\t\t\tcase 'y':\n\t\t\t\t\tthis.redo();\n\t\t\t\t\treturn true;\n\t\t\t\tcase 'a':\n\t\t\t\t\tthis.selectAll();\n\t\t\t\t\treturn true;\n\t\t\t\tcase 'd':\n\t\t\t\t\tthis.duplicate();\n\t\t\t\t\treturn true;\n\t\t\t\tcase 'g':\n\t\t\t\t\tinput.shift ? this.ungroup() : this.group();\n\t\t\t\t\treturn true;\n\t\t\t\tcase 'j':\n\t\t\t\t\treturn input.alt ? this.averageAnchors('both').ok : this.joinPaths().ok;\n\t\t\t\tcase ']':\n\t\t\t\t\tthis.arrange(input.alt ? 'front' : 'forward');\n\t\t\t\t\treturn true;\n\t\t\t\tcase '[':\n\t\t\t\t\tthis.arrange(input.alt ? 'back' : 'backward');\n\t\t\t\t\treturn true;\n\t\t\t\tcase '=':\n\t\t\t\tcase '+':\n\t\t\t\t\tthis.setViewport({ zoom: this.viewport.zoom * 1.25 });\n\t\t\t\t\treturn true;\n\t\t\t\tcase '-':\n\t\t\t\t\tthis.setViewport({ zoom: this.viewport.zoom / 1.25 });\n\t\t\t\t\treturn true;\n\t\t\t\tcase '0':\n\t\t\t\t\tthis.setViewport({ zoom: 1 });\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tswitch (key) {\n\t\t\tcase 'Escape':\n\t\t\t\tif (this.drag) this.cancelDrag();\n\t\t\t\telse if (this.widthEdit) this.closeWidthPointEdit();\n\t\t\t\telse if (this.tool === 'width' && this.widthPoints.length) {\n\t\t\t\t\tthis.widthPoints = [];\n\t\t\t\t\tthis.notify();\n\t\t\t\t}\n\t\t\t\telse if (this.pen) this.finishPath();\n\t\t\t\telse if (this.blendStart) {\n\t\t\t\t\tthis.blendStart = null;\n\t\t\t\t\tthis.notify();\n\t\t\t\t} else if (this.tool === 'direct' && this.anchors.length) {\n\t\t\t\t\tthis.anchors = [];\n\t\t\t\t\tthis.notify();\n\t\t\t\t} else if (this.tool !== 'select') this.setTool('select');\n\t\t\t\telse this.clearSelection();\n\t\t\t\treturn true;\n\t\t\tcase 'Enter':\n\t\t\t\tif (this.pen) {\n\t\t\t\t\tthis.finishPath();\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (this.tool === 'select' && this.selection.length === 1 && this.getNode(this.selection[0])?.type === 'path') {\n\t\t\t\t\tthis.tool = 'direct';\n\t\t\t\t\tthis.editingId = this.selection[0];\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\tcase 'Delete':\n\t\t\tcase 'Backspace':\n\t\t\t\tif (this.tool === 'width' && this.widthPoints.length) return this.removeWidthPoints().ok;\n\t\t\t\tif (this.pen) {\n\t\t\t\t\tconst node = this.getNode(this.pen.nodeId) as VectorPathNode;\n\t\t\t\t\tconst sp = node.subpaths[this.pen.subpath];\n\t\t\t\t\tif (sp.anchors.length > 2) this.deleteAnchors([{ nodeId: node.id, subpath: this.pen.subpath, index: sp.anchors.length - 1 }]);\n\t\t\t\t\telse {\n\t\t\t\t\t\tthis.removeNodes([node.id], { label: 'Delete Path' });\n\t\t\t\t\t\tthis.pen = null;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn this.deleteSelection().ok;\n\t\t\tcase 'ArrowLeft':\n\t\t\tcase 'ArrowRight':\n\t\t\tcase 'ArrowUp':\n\t\t\tcase 'ArrowDown': {\n\t\t\t\tconst step = input.shift ? 10 : 1;\n\t\t\t\tconst dx = key === 'ArrowLeft' ? -step : key === 'ArrowRight' ? step : 0;\n\t\t\t\tconst dy = key === 'ArrowUp' ? -step : key === 'ArrowDown' ? step : 0;\n\t\t\t\treturn this.translate(dx, dy).ok;\n\t\t\t}\n\t\t}\n\t\tif (key.length === 1 && !input.alt) {\n\t\t\tconst tool = TOOL_KEYS[lower];\n\t\t\tif (tool) {\n\t\t\t\tthis.setTool(tool);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tkeyUp(input: VectorKeyInput): void {\n\t\tif (input.key === ' ' && this.spaceHeld) {\n\t\t\tthis.spaceHeld = false;\n\t\t\tthis.notify();\n\t\t}\n\t}\n}\n\n/** \"Rectangle\", \"Square\", \"Ellipse\", \"Circle\", \"Line\", \"Triangle\" or \"Polygon\". */\nfunction inkShapeLabel(shape: VectorInkShape): string {\n\tconst name = vectorInkShapeName(shape);\n\treturn name[0].toUpperCase() + name.slice(1);\n}\n\n/** The subpath of a recognised pencil shape. */\nfunction inkShapeSubpath(shape: VectorInkShape): VectorSubpath {\n\tswitch (shape.kind) {\n\t\tcase 'line':\n\t\t\treturn lineSubpath({ x: shape.a[0], y: shape.a[1] }, { x: shape.b[0], y: shape.b[1] });\n\t\tcase 'rect':\n\t\t\treturn rectSubpath(shape);\n\t\tcase 'ellipse':\n\t\t\treturn ellipseSubpath(shape);\n\t\tcase 'polygon':\n\t\t\treturn { anchors: shape.points.map(([x, y]) => ({ x, y })), closed: true };\n\t}\n}\n\n/** Snaps `p` to the nearest 45° direction from `origin`, keeping its distance. */\nfunction constrain45(origin: Vec2, p: Vec2): Vec2 {\n\tconst dx = p.x - origin.x;\n\tconst dy = p.y - origin.y;\n\tconst len = Math.hypot(dx, dy);\n\tconst a = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);\n\treturn { x: origin.x + Math.cos(a) * len, y: origin.y + Math.sin(a) * len };\n}\n\n/** Scale factors and origin for dragging a selection-box handle to `p`. */\nexport function scaleFor(handle: VectorTransformHandle, b: VectorBounds, p: Vec2, mods: VectorModifiers = {}): { sx: number; sy: number; origin: Vec2 } {\n\tconst west = handle.includes('w');\n\tconst east = handle.includes('e');\n\tconst north = handle.startsWith('n');\n\tconst south = handle.startsWith('s');\n\tconst center = { x: b.x + b.width / 2, y: b.y + b.height / 2 };\n\tconst origin = mods.alt ? center : { x: west ? b.x + b.width : east ? b.x : center.x, y: north ? b.y + b.height : south ? b.y : center.y };\n\tconst reachX = mods.alt ? b.width / 2 : b.width;\n\tconst reachY = mods.alt ? b.height / 2 : b.height;\n\tlet sx = west || east ? (reachX > 1e-9 ? ((p.x - origin.x) * (west ? -1 : 1)) / reachX : 1) : 1;\n\tlet sy = north || south ? (reachY > 1e-9 ? ((p.y - origin.y) * (north ? -1 : 1)) / reachY : 1) : 1;\n\tif (mods.shift) {\n\t\tif ((west || east) && (north || south)) {\n\t\t\tconst s = Math.max(Math.abs(sx), Math.abs(sy));\n\t\t\tsx = Math.sign(sx || 1) * s;\n\t\t\tsy = Math.sign(sy || 1) * s;\n\t\t} else if (west || east) sy = Math.abs(sx);\n\t\telse sx = Math.abs(sy);\n\t}\n\treturn { sx, sy, origin };\n}\n\n/** A style applied to a path; a `null` (or empty) width profile removes it. */\nfunction withStyle(node: VectorPathNode, style: VectorStyle & { opacity?: number }): VectorNode {\n\tconst next = { ...node, ...style } as VectorPathNode;\n\tif ('strokeWidthProfile' in style) {\n\t\tconst p = style.strokeWidthProfile ? sanitizeWidthProfile(style.strokeWidthProfile) : [];\n\t\tif (p.length) next.strokeWidthProfile = p;\n\t\telse delete next.strokeWidthProfile;\n\t}\n\treturn next;\n}\n\n/** True when node `id` or one of its ancestors is locked or hidden. */\nfunction hiddenOrLocked(nodes: readonly VectorNode[], id: string): boolean {\n\tconst loc = locateVectorNode(nodes as VectorNode[], id);\n\tif (!loc) return false;\n\tconst n = loc.list[loc.index];\n\tif (n.locked || n.visible === false) return true;\n\treturn loc.parent ? hiddenOrLocked(nodes, loc.parent.id) : false;\n}\n\n/** The width profile of pencil samples and their pressures (positions by length along the samples). */\nfunction pencilPressureProfile(points: readonly Point[], pressures: readonly number[]): VectorWidthPoint[] {\n\tconst n = Math.min(points.length, pressures.length);\n\tif (n < 2) return [];\n\tconst s = [0];\n\tfor (let i = 1; i < n; i++) s.push(s[i - 1] + Math.hypot(points[i][0] - points[i - 1][0], points[i][1] - points[i - 1][1]));\n\tconst total = s[n - 1];\n\tif (!(total > 0)) return [];\n\treturn pressureWidthProfile(s.map((d, i) => ({ t: d / total, pressure: pressures[i] })));\n}\n","/**\n * Framework-neutral render data for a vector editor: the scene (document-space SVG paths and\n * gradient defs) and the overlay (screen-space selection box, anchors, handles, marquee and\n * pen preview). React, Svelte or plain DOM code turns these into `<svg>` elements.\n */\nimport { bezierPathToSvg, transformBezierPath, type BezierPath } from '../canvas/pathops/bezier.js';\nimport { hatchPath } from '../canvas/pathops/hatch.js';\nimport type { SvgGradientDef } from '../canvas/gradient.js';\n\nexport type { SvgGradientDef };\nimport {\n\tgetAnchorWorld,\n\tgetVectorCornerWidgets,\n\tgetVectorSelectionBoxGeometry,\n\tgetVectorWidthHandles,\n\tgetVisibleAnchors,\n\tgetVisibleHandles,\n\ttype VectorEditorController,\n\ttype VectorEditorState,\n\ttype VectorSelectionBoxGeometry,\n\ttype VectorWidthPointRef\n} from './controller.js';\nimport { sanitizeWidthProfile, widthProfilePresetId, type VectorWidthPreset } from './width.js';\nimport {\n\tfindVectorNode,\n\tlocateVectorNode,\n\tsubpathsToBezier,\n\tvectorWorldMatrix,\n\tIDENTITY_MATRIX,\n\tinferAnchorType,\n\tmultiplyMatrices,\n\tnodeMatrix,\n\tvectorNodeBounds,\n\tvectorParentMatrix,\n\tvectorPathData,\n\tvectorPathWorldBezier,\n\ttype AffineMatrix\n} from './geometry.js';\nimport { vectorPaintToSvg, vectorStrokeSvgParts, type VectorRenderStrokeParts } from './svg.js';\nimport { vectorCorners } from './corners.js';\nimport {\n\texpandVectorAppearance,\n\thasOwnVectorAppearance,\n\thasVectorAppearance,\n\tvectorAppearanceOutline,\n\tvectorEnteredGroups,\n\tVECTOR_EFFECT_LABELS,\n\tVECTOR_EFFECT_PARAM_SPECS,\n\ttype VectorEffectParamSpec\n} from './appearance.js';\nimport { VECTOR_EFFECT_TYPES, type VectorEffectType } from './types.js';\nimport { isVectorBlendGroup, resolveVectorBlendOptions, vectorBlendRailAt, vectorBlendSpine } from './blendGroup.js';\nimport type { VectorGuideKind, VectorSnapKind } from './snap.js';\nimport type {\n\tVectorAnchorRef,\n\tVectorAnchorType,\n\tVectorArrowAlign,\n\tVectorArrowhead,\n\tVectorBlendOptions,\n\tVectorBlendSpacing,\n\tVectorBounds,\n\tVectorCornerKind,\n\tVectorGroupNode,\n\tVectorHandleRef,\n\tVectorNode,\n\tVectorPaint,\n\tVectorPathfinderOperation,\n\tVectorPathNode,\n\tVectorStrokeAlign,\n\tVectorTransformHandle,\n\tVectorWidthPoint,\n\tVec2\n} from './types.js';\n\nexport interface VectorRenderPath {\n\tkind: 'path';\n\tid: string;\n\t/** SVG `transform` (`matrix(…)`) for a rotated node. */\n\ttransform?: string;\n\td: string;\n\t/** `none`, a colour, or `url(#id)` of a gradient in `defs`. */\n\tfill: string;\n\tstroke: string;\n\tstrokeWidth: number;\n\tstrokeLinecap?: string;\n\tstrokeLinejoin?: string;\n\tstrokeDasharray?: string;\n\tfillRule: 'nonzero' | 'evenodd';\n\topacity: number;\n\t/**\n\t * Set when the stroke takes more than one element: an inside / outside alignment or\n\t * arrowheads. Draw `d` with the fill only, then `stroke.d` with the stroke (at\n\t * `stroke.width`, clipped or masked as `stroke.clip` says), then each head filled with\n\t * `stroke`; group them under `id`, `transform` and `opacity`.\n\t */\n\tstrokeParts?: VectorRenderStrokeParts;\n}\n\nexport interface VectorRenderGroup {\n\tkind: 'group';\n\tid: string;\n\t/** SVG `transform` (`matrix(…)`) for a rotated group. */\n\ttransform?: string;\n\topacity: number;\n\tchildren: VectorRenderNode[];\n}\n\nexport type VectorRenderNode = VectorRenderPath | VectorRenderGroup;\n\nexport interface VectorSceneRenderData {\n\t/** SVG `transform` for the document layer: `translate(x y) scale(zoom)`. */\n\ttransform: string;\n\twidth: number;\n\theight: number;\n\tbackground?: string;\n\t/** Gradients referenced by `fill` / `stroke` values. */\n\tdefs: SvgGradientDef[];\n\t/** Visible nodes, bottom to top. */\n\tnodes: VectorRenderNode[];\n}\n\nexport interface VectorRenderAnchor {\n\tref: VectorAnchorRef;\n\t/** Screen position. */\n\tx: number;\n\ty: number;\n\ttype: VectorAnchorType;\n\tselected: boolean;\n\thovered: boolean;\n\t/** The pen tool closes the path when this anchor is clicked. */\n\tcloses: boolean;\n}\n\nexport interface VectorRenderHandle {\n\tref: VectorHandleRef;\n\tx: number;\n\ty: number;\n\t/** Screen position of the handle's anchor (draw a line to it). */\n\tanchorX: number;\n\tanchorY: number;\n\thovered: boolean;\n}\n\n/** A live corner widget in screen pixels. */\nexport interface VectorRenderCornerWidget {\n\tref: VectorAnchorRef;\n\tx: number;\n\ty: number;\n\tradius: number;\n\tkind: VectorCornerKind;\n\thovered: boolean;\n\t/** Its corners are being dragged. */\n\tactive: boolean;\n}\n\n/**\n * A width point of the width tool in screen pixels: a diamond at (`x`, `y`) on the path and a\n * line across the stroke from `left` to `right`, with a handle at each end.\n */\nexport interface VectorRenderWidthPoint {\n\tref: VectorWidthPointRef;\n\tx: number;\n\ty: number;\n\tleft: Vec2;\n\tright: Vec2;\n\tselected: boolean;\n\t/** The part under the pointer, if any. */\n\thovered: 'center' | 'left' | 'right' | null;\n\t/** Being dragged. */\n\tactive: boolean;\n}\n\n/** A smart guide line in screen pixels. */\nexport interface VectorRenderGuideLine {\n\tkind: VectorGuideKind;\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n}\n\n/** A smart guide label (\"anchor\", \"intersect\", \"align\", \"centre\", a gap or a size) at a screen position. */\nexport interface VectorRenderGuideLabel {\n\tkind: VectorSnapKind;\n\tx: number;\n\ty: number;\n\ttext: string;\n}\n\nexport interface VectorOverlayRenderData {\n\t/** Outlines (screen-space path data) of hovered and selected nodes. */\n\toutlines: { id: string; d: string; selected: boolean }[];\n\t/**\n\t * Selection box with resize and rotate handles (select tool). `x / y / width / height` and\n\t * each handle's `x / y` are the unrotated box in screen pixels: draw them inside\n\t * `<g transform={transform}>`. `corners` and each handle's `screen` are true positions.\n\t */\n\tselectionBox:\n\t\t| (VectorBounds & { angle: number; transform: string; corners: Vec2[]; handles: VectorSelectionBoxGeometry['handles'] })\n\t\t| null;\n\tanchors: VectorRenderAnchor[];\n\thandles: VectorRenderHandle[];\n\t/** Marquee rectangle, screen space. */\n\tmarquee: VectorBounds | null;\n\t/** Rubber-band segment from the pen path's last anchor to the pointer, screen space. */\n\tpenPreview: string | null;\n\t/** Artboard rectangle, screen space. */\n\tartboard: VectorBounds;\n\t/** Smart guides of the snap in progress, screen space (empty when nothing snapped). */\n\tguides: { lines: VectorRenderGuideLine[]; labels: VectorRenderGuideLabel[] };\n\t/**\n\t * The knife or eraser stroke being drawn, screen space: `d` is an open polyline; draw the\n\t * eraser's with a round-capped stroke `width` pixels wide (0 for the knife: a thin line).\n\t */\n\ttoolStroke: { tool: 'knife' | 'eraser'; d: string; width: number } | null;\n\t/** The pencil's lazy-mouse string from the pen to the pointer, screen space. */\n\tpencilString: { x1: number; y1: number; x2: number; y2: number } | null;\n\t/** Where the scissors would cut (screen space). */\n\tcutPoint: Vec2 | null;\n\t/**\n\t * Live corner widgets of the direct tool (screen space): drag one to round its corners,\n\t * Alt-click to cycle round / inverted / chamfer, click to pick its corner alone.\n\t */\n\tcornerWidgets: VectorRenderCornerWidget[];\n\t/** Width points of the width tool (screen space). */\n\twidthPoints: VectorRenderWidthPoint[];\n\t/**\n\t * Where a width-tool drag would add a width point (screen space): draw a dashed line from\n\t * `left` to `right` and a hollow diamond at (`x`, `y`).\n\t */\n\twidthSpot: { x: number; y: number; left: Vec2; right: Vec2 } | null;\n\t/**\n\t * Shape Builder and paint bucket highlight (screen space): `faces` are filled paths (fill\n\t * them translucent and outline them), `hatches` the 45° hatch lines over each face\n\t * (`hatchPath`, a few pixels apart; merge and erase only, empty strings for the paint\n\t * bucket: stroke them over the translucent fill), `lines` are the line pieces and outline\n\t * edges an erase deletes, `trail` is the drag's polyline. `mode` is `merge`, `erase` (Alt)\n\t * or `fill` (paint bucket).\n\t */\n\tbuilder: { mode: 'merge' | 'erase' | 'fill'; faces: string[]; hatches: string[]; lines: string[]; trail: string | null } | null;\n\t/**\n\t * Blends (screen space): the spine of each selected blend (`d`, draw it dashed; `keys` are\n\t * where its keys sit on it; `replaced` when it is a replaced spine) and, with the Blend tool,\n\t * the outline of the shape it blends from (`start`, with the clicked anchor) and the anchors\n\t * of the first subpath under the pointer (click one to start the blend there).\n\t */\n\tblend: {\n\t\tspines: { id: string; d: string; keys: Vec2[]; replaced: boolean }[];\n\t\tstart: { d: string; anchor: Vec2 | null } | null;\n\t\tanchors: { x: number; y: number; hovered: boolean }[];\n\t};\n\t/** A short status, e.g. \"W 120 × H 80\" or \"45°\". */\n\tstatus: string | null;\n}\n\nconst fmt = (n: number) => Math.round(n * 100) / 100;\nconst fmtMatrix = (m: AffineMatrix) => `matrix(${m.map((n) => Math.round(n * 1e6) / 1e6).join(' ')})`;\n/** Distance between the Shape Builder's hatch lines over a face (screen pixels). */\nexport const VECTOR_BUILDER_HATCH_GAP = 6;\n\nfunction renderNode(stored: VectorNode, defs: SvgGradientDef[], idPrefix: string): VectorRenderNode | null {\n\tif (stored.visible === false) return null;\n\t// Path effects and live booleans draw as their expanded shapes.\n\tconst node = expandVectorAppearance(stored);\n\tif (node.type === 'group') {\n\t\tconst children = node.children.map((c) => renderNode(c, defs, idPrefix)).filter((c): c is VectorRenderNode => !!c);\n\t\treturn { kind: 'group', id: node.id, ...(node.transform ? { transform: fmtMatrix(node.transform) } : {}), opacity: node.opacity ?? 1, children };\n\t}\n\tconst fill = vectorPaintToSvg(node.fill, `${idPrefix}fill-${node.id}`);\n\tconst stroke = vectorPaintToSvg(node.stroke, `${idPrefix}stroke-${node.id}`);\n\tif (fill.gradient) defs.push(fill.gradient);\n\tif (stroke.gradient) defs.push(stroke.gradient);\n\tconst strokeParts = stroke.value !== 'none' ? vectorStrokeSvgParts(node, `${idPrefix}clip-${node.id}`) : undefined;\n\treturn {\n\t\tkind: 'path',\n\t\tid: node.id,\n\t\t...(node.transform ? { transform: fmtMatrix(node.transform) } : {}),\n\t\t...(strokeParts ? { strokeParts } : {}),\n\t\td: vectorPathData(node),\n\t\tfill: fill.value,\n\t\tstroke: stroke.value,\n\t\tstrokeWidth: node.strokeWidth ?? 1,\n\t\t...(node.strokeLinecap ? { strokeLinecap: node.strokeLinecap } : {}),\n\t\t...(node.strokeLinejoin ? { strokeLinejoin: node.strokeLinejoin } : {}),\n\t\t...(node.strokeDasharray?.length ? { strokeDasharray: node.strokeDasharray.join(' ') } : {}),\n\t\tfillRule: node.fillRule ?? 'nonzero',\n\t\topacity: node.opacity ?? 1\n\t};\n}\n\n/**\n * The document layer. Render the nodes inside `<g transform={transform}>` and `defs` in a\n * `<defs>` block. Pass a unique `idPrefix` when several editors share a page.\n */\nexport function getVectorSceneRenderData(state: Pick<VectorEditorState, 'document' | 'viewport'>, options: { idPrefix?: string } = {}): VectorSceneRenderData {\n\tconst defs: SvgGradientDef[] = [];\n\tconst prefix = options.idPrefix ?? 've-';\n\tconst nodes = state.document.nodes.map((n) => renderNode(n, defs, prefix)).filter((n): n is VectorRenderNode => !!n);\n\tconst v = state.viewport;\n\treturn {\n\t\ttransform: `translate(${fmt(v.x)} ${fmt(v.y)}) scale(${v.zoom})`,\n\t\twidth: state.document.width,\n\t\theight: state.document.height,\n\t\t...(state.document.background ? { background: state.document.background } : {}),\n\t\tdefs,\n\t\tnodes\n\t};\n}\n\nconst toScreen = (state: Pick<VectorEditorState, 'viewport'>, p: Vec2) => ({ x: p.x * state.viewport.zoom + state.viewport.x, y: p.y * state.viewport.zoom + state.viewport.y });\n\nfunction screenOutline(state: VectorEditorState, node: VectorNode, entered: ReadonlySet<string>): string {\n\tconst v = state.viewport;\n\tconst view: AffineMatrix = [v.zoom, 0, 0, v.zoom, v.x, v.y];\n\tconst parent = vectorParentMatrix(state.document.nodes, node.id) ?? IDENTITY_MATRIX;\n\treturn outlinePaths(node, parent, entered)\n\t\t.map((path) => bezierPathToSvg(transformBezierPath(path, view), { precision: 2 }))\n\t\t.join(' ');\n}\n\n/**\n * Document-space outlines of a subtree: paths as stored (their anchors are what is edited; the\n * scene shows their effects), live groups as drawn unless they are being edited (then their\n * children, as figcraft outlines a boolean group's operands).\n */\nfunction outlinePaths(node: VectorNode, parent: AffineMatrix, entered: ReadonlySet<string>): BezierPath[] {\n\tif (node.type === 'path') return [vectorPathWorldBezier(node, parent, { effects: false })];\n\tconst m = multiplyMatrices(nodeMatrix(node), parent);\n\tif (hasOwnVectorAppearance(node) && !entered.has(node.id)) return [transformBezierPath(vectorAppearanceOutline(node), m)];\n\treturn node.children.filter((c) => c.visible !== false).flatMap((c) => outlinePaths(c, m, entered));\n}\n\n/** Live groups being edited in `state` (see `vectorEnteredGroups`). */\nexport function getVectorEnteredGroups(state: Pick<VectorEditorState, 'document' | 'selection' | 'editingId'>): Set<string> {\n\treturn vectorEnteredGroups(state.document.nodes, state.editingId ? [...state.selection, state.editingId] : state.selection);\n}\n\nfunction collect(node: VectorNode): VectorPathNode[] {\n\tif (node.type === 'path') return [node];\n\treturn node.children.flatMap(collect);\n}\n\n/** The editing overlay in screen pixels. Render it on top of the scene, untransformed. */\nexport function getVectorOverlayRenderData(state: VectorEditorState): VectorOverlayRenderData {\n\tconst doc = state.document;\n\tconst z = state.viewport.zoom;\n\tconst outlines: VectorOverlayRenderData['outlines'] = [];\n\tconst entered = getVectorEnteredGroups(state);\n\tconst seen = new Set<string>();\n\tconst outline = (id: string, selected: boolean) => {\n\t\tif (seen.has(id)) return;\n\t\tconst node = findVectorNode(doc.nodes, id);\n\t\tif (!node || node.visible === false) return;\n\t\tseen.add(id);\n\t\toutlines.push({ id, d: screenOutline(state, node, entered), selected });\n\t};\n\tfor (const id of state.selection) outline(id, true);\n\tif (state.editingId) outline(state.editingId, true);\n\tif (state.hover?.nodeId && !state.interaction) outline(state.hover.nodeId, false);\n\n\tlet selectionBox: VectorOverlayRenderData['selectionBox'] = null;\n\tif (state.tool === 'select' && state.selectionFrame && state.interaction?.type !== 'marquee') {\n\t\tconst g = getVectorSelectionBoxGeometry(state.selectionFrame, state.viewport);\n\t\tselectionBox = { ...g.box, angle: g.angle, transform: g.transform, corners: g.corners, handles: g.handles };\n\t}\n\n\tconst selected = (r: VectorAnchorRef) => state.selectedAnchors.some((a) => a.nodeId === r.nodeId && a.subpath === r.subpath && a.index === r.index);\n\tconst sameAnchor = (a: VectorAnchorRef | undefined, b: VectorAnchorRef) => !!a && a.nodeId === b.nodeId && a.subpath === b.subpath && a.index === b.index;\n\tconst anchors: VectorRenderAnchor[] = getVisibleAnchors(state).map((ref) => {\n\t\tconst a = getAnchorWorld(doc, ref)!;\n\t\tconst s = toScreen(state, a);\n\t\tconst closes = !!state.pen && state.pen.nodeId === ref.nodeId && state.pen.subpath === ref.subpath && ref.index === 0;\n\t\treturn { ref, x: s.x, y: s.y, type: a.type ?? inferAnchorType(a), selected: selected(ref), hovered: sameAnchor(state.hover?.anchor, ref), closes };\n\t});\n\tif (state.tool === 'pen' && !state.pen && state.hover?.anchor) {\n\t\tconst a = getAnchorWorld(doc, state.hover.anchor);\n\t\tif (a) {\n\t\t\tconst s = toScreen(state, a);\n\t\t\tanchors.push({ ref: state.hover.anchor, x: s.x, y: s.y, type: a.type ?? 'corner', selected: false, hovered: true, closes: false });\n\t\t}\n\t}\n\n\tconst handles: VectorRenderHandle[] = getVisibleHandles(state).flatMap((ref) => {\n\t\tconst a = getAnchorWorld(doc, ref);\n\t\tconst h = a?.[ref.handle];\n\t\tif (!a || !h) return [];\n\t\tconst s = toScreen(state, h);\n\t\tconst sa = toScreen(state, a);\n\t\tconst hovered = !!state.hover?.handle && sameAnchor(state.hover.handle, ref) && state.hover.handle.handle === ref.handle;\n\t\treturn [{ ref, x: s.x, y: s.y, anchorX: sa.x, anchorY: sa.y, hovered }];\n\t});\n\n\tlet marquee: VectorBounds | null = null;\n\tif (state.interaction?.type === 'marquee') {\n\t\tconst b = state.interaction.bounds;\n\t\tconst tl = toScreen(state, b);\n\t\tmarquee = { x: tl.x, y: tl.y, width: b.width * z, height: b.height * z };\n\t}\n\n\tlet penPreview: string | null = null;\n\tif (state.tool === 'pen' && state.pen && state.pointer && !state.interaction) {\n\t\tconst node = findVectorNode(doc.nodes, state.pen.nodeId);\n\t\tconst sp = node?.type === 'path' ? node.subpaths[state.pen.subpath] : undefined;\n\t\tconst last = sp ? getAnchorWorld(doc, { nodeId: state.pen.nodeId, subpath: state.pen.subpath, index: sp.anchors.length - 1 }) : undefined;\n\t\tif (last) {\n\t\t\tconst a = toScreen(state, last);\n\t\t\tconst p = toScreen(state, state.pointer);\n\t\t\tconst c = last.out ? toScreen(state, last.out) : null;\n\t\t\tpenPreview = c ? `M${fmt(a.x)} ${fmt(a.y)}Q${fmt(c.x)} ${fmt(c.y)} ${fmt(p.x)} ${fmt(p.y)}` : `M${fmt(a.x)} ${fmt(a.y)}L${fmt(p.x)} ${fmt(p.y)}`;\n\t\t}\n\t}\n\n\tconst origin = toScreen(state, { x: 0, y: 0 });\n\tconst artboard = { x: origin.x, y: origin.y, width: doc.width * z, height: doc.height * z };\n\n\tlet status: string | null = null;\n\tconst it = state.interaction;\n\tif (it?.type === 'rotate') status = `${Math.round(it.angle)}°`;\n\telse if (it?.type === 'move' || it?.type === 'anchors') status = `Δ ${fmt(it.dx)}, ${fmt(it.dy)}`;\n\telse if (it?.type === 'scale' || it?.type === 'draw') {\n\t\tconst b = it.type === 'draw' ? (findVectorNode(doc.nodes, it.nodeId) ? vectorNodeBounds(findVectorNode(doc.nodes, it.nodeId)!) : null) : state.selectionFrame;\n\t\tif (b) status = `W ${fmt(b.width)} × H ${fmt(b.height)}`;\n\t}\n\n\tconst guides: VectorOverlayRenderData['guides'] = { lines: [], labels: [] };\n\tif (state.guides) {\n\t\tfor (const l of state.guides.lines) {\n\t\t\tconst a = toScreen(state, { x: l.x1, y: l.y1 });\n\t\t\tconst b = toScreen(state, { x: l.x2, y: l.y2 });\n\t\t\tguides.lines.push({ kind: l.kind, x1: fmt(a.x), y1: fmt(a.y), x2: fmt(b.x), y2: fmt(b.y) });\n\t\t}\n\t\tfor (const l of state.guides.labels) {\n\t\t\tconst p = toScreen(state, l);\n\t\t\tguides.labels.push({ kind: l.kind, x: fmt(p.x), y: fmt(p.y), text: l.text });\n\t\t}\n\t}\n\n\tlet toolStroke: VectorOverlayRenderData['toolStroke'] = null;\n\tif (it?.type === 'stroke') {\n\t\tconst pts = it.points.map((q) => toScreen(state, q));\n\t\tconst d = pts.map((q, i) => `${i ? 'L' : 'M'}${fmt(q.x)} ${fmt(q.y)}`).join('') + (pts.length === 1 ? `L${fmt(pts[0].x)} ${fmt(pts[0].y)}` : '');\n\t\ttoolStroke = { tool: it.tool, d, width: fmt(it.width * z) };\n\t}\n\tlet pencilString: VectorOverlayRenderData['pencilString'] = null;\n\tif (it?.type === 'draw' && it.string) {\n\t\tconst a = toScreen(state, it.string[0]);\n\t\tconst b = toScreen(state, it.string[1]);\n\t\tpencilString = { x1: fmt(a.x), y1: fmt(a.y), x2: fmt(b.x), y2: fmt(b.y) };\n\t}\n\tconst dragged = it?.type === 'corner' ? it.ref : null;\n\tconst cornerWidgets: VectorRenderCornerWidget[] = getVectorCornerWidgets(state).map((w) => {\n\t\tconst s = toScreen(state, w);\n\t\treturn { ref: w.ref, x: fmt(s.x), y: fmt(s.y), radius: w.radius, kind: w.kind, hovered: sameAnchor(state.hover?.corner, w.ref), active: !!dragged };\n\t});\n\tif (it?.type === 'corner') status = `R ${fmt(it.radius)}`;\n\tconst cutPoint = state.tool === 'scissors' && state.hover?.segment && !it ? (({ x, y }) => ({ x: fmt(x), y: fmt(y) }))(toScreen(state, state.hover.segment.point)) : null;\n\n\tconst sc = (p: Vec2) => {\n\t\tconst q = toScreen(state, p);\n\t\treturn { x: fmt(q.x), y: fmt(q.y) };\n\t};\n\tconst sameWidth = (a: VectorWidthPointRef | null | undefined, b: VectorWidthPointRef) => !!a && a.nodeId === b.nodeId && a.index === b.index;\n\tconst widthPoints: VectorRenderWidthPoint[] = getVectorWidthHandles(state).map((h) => {\n\t\tconst c = sc(h);\n\t\tconst hw = state.hover?.widthPoint;\n\t\tconst hovered = hw && sameWidth(hw, h.ref) && hw.subpath === h.ref.subpath ? hw.part : null;\n\t\treturn { ref: h.ref, x: c.x, y: c.y, left: sc(h.left), right: sc(h.right), selected: h.selected, hovered, active: it?.type === 'width' && sameWidth(it.ref, h.ref) };\n\t});\n\tconst spot = state.tool === 'width' && !it ? state.hover?.strokeSpot : null;\n\tconst widthSpot = spot ? { ...sc(spot.point), left: sc(spot.left), right: sc(spot.right) } : null;\n\tif (it?.type === 'width') status = it.mode === 'move' ? 'Move width point' : it.mode === 'copy' ? 'Copy width point' : `W ${fmt(it.width)}`;\n\n\tlet builder: VectorOverlayRenderData['builder'] = null;\n\tif (state.builder) {\n\t\tconst v = state.viewport;\n\t\tconst view: AffineMatrix = [v.zoom, 0, 0, v.zoom, v.x, v.y];\n\t\tconst svg = (path: BezierPath) => bezierPathToSvg(path, { precision: 2 });\n\t\tconst screen = state.builder.faces.map((f) => transformBezierPath(f, view));\n\t\tconst hatch = state.builder.mode !== 'fill';\n\t\tconst trail = state.builder.trail && state.builder.trail.length > 1 ? state.builder.trail.map((q, i) => `${i ? 'L' : 'M'}${sc(q).x} ${sc(q).y}`).join('') : null;\n\t\tbuilder = {\n\t\t\tmode: state.builder.mode,\n\t\t\tfaces: screen.map(svg),\n\t\t\thatches: screen.map((f) => (hatch ? svg(hatchPath(f, { gap: VECTOR_BUILDER_HATCH_GAP })) : '')),\n\t\t\tlines: state.builder.lines.map((l) => svg(transformBezierPath(l, view))),\n\t\t\ttrail\n\t\t};\n\t\tif (state.builder.mode !== 'fill' && it?.type === 'builder') {\n\t\t\tconst n = state.builder.faces.length;\n\t\t\tstatus = state.builder.mode === 'erase' ? `Delete ${n} ${n === 1 ? 'face' : 'faces'}` : `Merge ${n} ${n === 1 ? 'face' : 'faces'}`;\n\t\t}\n\t}\n\n\tconst blend = blendOverlay(state);\n\tif (state.tool === 'blend' && !it) status = state.blendStart ? 'Click another shape to blend into' : 'Click a shape to blend from';\n\n\treturn { outlines, selectionBox, anchors, handles, marquee, penPreview, artboard, guides, status, toolStroke, pencilString, cutPoint, cornerWidgets, widthPoints, widthSpot, builder, blend };\n}\n\n/** Blend groups the selection covers (a selected key counts as its blend). */\nfunction selectedBlends(state: Pick<VectorEditorState, 'document' | 'selection'>): VectorGroupNode[] {\n\tconst out = new Map<string, VectorGroupNode>();\n\tfor (const id of state.selection) {\n\t\tconst node = findVectorNode(state.document.nodes, id);\n\t\tconst group = isVectorBlendGroup(node) ? node : locateVectorNode(state.document.nodes, id)?.parent;\n\t\tif (isVectorBlendGroup(group)) out.set(group.id, group);\n\t}\n\treturn [...out.values()];\n}\n\nfunction blendOverlay(state: VectorEditorState): VectorOverlayRenderData['blend'] {\n\tconst v = state.viewport;\n\tconst view: AffineMatrix = [v.zoom, 0, 0, v.zoom, v.x, v.y];\n\tconst doc = state.document;\n\tconst sc = (p: Vec2, m: AffineMatrix) => {\n\t\tconst q = { x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] };\n\t\treturn { x: fmt(q.x), y: fmt(q.y) };\n\t};\n\tconst spines = selectedBlends(state).flatMap((g) => {\n\t\tconst s = vectorBlendSpine(g);\n\t\tif (!s) return [];\n\t\tconst m = multiplyMatrices(vectorWorldMatrix(doc.nodes, g.id) ?? IDENTITY_MATRIX, view);\n\t\tconst d = bezierPathToSvg(transformBezierPath(subpathsToBezier([s.spine], { corners: false }), m), { precision: 2 });\n\t\tconst keys = s.stops.map((stop) => {\n\t\t\tconst p = vectorBlendRailAt(s.rail, stop).point;\n\t\t\treturn sc({ x: p[0], y: p[1] }, m);\n\t\t});\n\t\treturn [{ id: g.id, d, keys, replaced: s.replaced }];\n\t});\n\tlet start: VectorOverlayRenderData['blend']['start'] = null;\n\tconst anchors: VectorOverlayRenderData['blend']['anchors'] = [];\n\tif (state.tool === 'blend') {\n\t\tconst from = state.blendStart ? findVectorNode(doc.nodes, state.blendStart.nodeId) : undefined;\n\t\tif (from && state.blendStart) {\n\t\t\tconst a = state.blendStart.anchor === null ? undefined : getAnchorWorld(doc, { nodeId: from.id, subpath: 0, index: state.blendStart.anchor });\n\t\t\tstart = { d: screenOutline(state, from, new Set()), anchor: a ? sc(a, view) : null };\n\t\t}\n\t\tconst hovered = state.hover?.nodeId ? findVectorNode(doc.nodes, state.hover.nodeId) : undefined;\n\t\tif (hovered?.type === 'path' && hovered.subpaths[0]) {\n\t\t\thovered.subpaths[0].anchors.forEach((_, index) => {\n\t\t\t\tconst a = getAnchorWorld(doc, { nodeId: hovered.id, subpath: 0, index });\n\t\t\t\tif (a) anchors.push({ ...sc(a, view), hovered: state.hover?.anchor?.index === index });\n\t\t\t});\n\t\t}\n\t}\n\treturn { spines, start, anchors };\n}\n\n/** What the Blend controls of a toolbar or style panel show. */\nexport interface VectorBlendControls {\n\t/** Ids of the selected blends (a selected key counts as its blend). */\n\tids: string[];\n\t/** The first selected blend's options, or the options new blends get. */\n\toptions: Required<VectorBlendOptions>;\n\t/** Two or more shapes that share a parent are selected: Blend can run. */\n\tcanBlend: boolean;\n\t/** One blend and one path are selected: Replace Spine can run. */\n\tcanReplaceSpine: boolean;\n\t/** The first selected blend follows a replaced spine. */\n\thasSpine: boolean;\n}\n\n/** State of the Blend controls for the current selection. */\nexport function getVectorBlendControls(state: Pick<VectorEditorState, 'document' | 'selection' | 'blendDefaults'>): VectorBlendControls {\n\tconst blends = selectedBlends(state);\n\tconst doc = state.document;\n\tconst roots = state.selection.filter((id) => {\n\t\tconst node = findVectorNode(doc.nodes, id);\n\t\treturn !!node && !node.blendStep && !node.locked && !state.selection.includes(locateVectorNode(doc.nodes, id)?.parent?.id ?? '');\n\t});\n\tconst parents = new Set(roots.map((id) => locateVectorNode(doc.nodes, id)?.parent?.id ?? null));\n\tconst paths = roots.filter((id) => findVectorNode(doc.nodes, id)?.type === 'path' && !isVectorBlendGroup(locateVectorNode(doc.nodes, id)?.parent));\n\tconst first = blends[0];\n\treturn {\n\t\tids: blends.map((g) => g.id),\n\t\toptions: resolveVectorBlendOptions(first ? first.blend : state.blendDefaults),\n\t\tcanBlend: roots.length >= 2 && parents.size === 1,\n\t\tcanReplaceSpine: blends.length === 1 && paths.length === 1,\n\t\thasSpine: !!first?.blend?.spine\n\t};\n}\n\n/**\n * Applies blend options from a toolbar: to the selected blends (one undo step) and to the\n * options new blends get, so the controls keep showing what was picked.\n */\nexport function applyVectorBlendOptions(controller: VectorEditorController, patch: Partial<VectorBlendOptions>): void {\n\tcontroller.setBlendDefaults(patch);\n\tif (getVectorBlendControls(controller.getState()).ids.length) controller.setBlendOptions(patch);\n}\n\n/** Labels of the blend spacing modes, for toolbars. */\nexport const VECTOR_BLEND_SPACING_LABELS: Readonly<Record<VectorBlendSpacing, string>> = { steps: 'Specified Steps', distance: 'Specified Distance', smoothColor: 'Smooth Color' };\n\n// ---------------------------------------------------------------------------\n// Event adapters (duck-typed, so they work with DOM, React and Svelte events)\n// ---------------------------------------------------------------------------\n\n/** Colours and sizes (screen pixels) the bundled canvases draw the overlay with. */\nexport const VECTOR_OVERLAY_THEME = {\n\taccent: '#0ea5e9',\n\t/** Smart guide lines and labels. */\n\tguide: '#ff3dfc',\n\thandleFill: '#ffffff',\n\tmarqueeFill: 'rgba(14, 165, 233, 0.08)',\n\t/** The eraser's swath while it is dragged. */\n\teraserFill: 'rgba(239, 68, 68, 0.25)',\n\ttransformHandleSize: 8,\n\tanchorSize: 7,\n\thandleRadius: 3.5,\n\t/** Radius of a live corner widget (a ring with a dot). */\n\tcornerWidgetRadius: 4,\n\t/** Half the diagonal of a width point's diamond. */\n\twidthPointSize: 5,\n\t/** Shape Builder faces to merge and paint bucket regions. */\n\tbuilderFill: 'rgba(14, 165, 233, 0.22)',\n\t/** Shape Builder faces and line pieces an Alt gesture deletes. */\n\tbuilderErase: '#ef4444',\n\tbuilderEraseFill: 'rgba(239, 68, 68, 0.22)',\n\t/** Opacity of the hatch lines over Shape Builder faces (drawn in the face's outline colour). */\n\tbuilderHatchOpacity: 0.6\n} as const;\n\n/** SVG path data of the diamond a width point is drawn with (screen pixels). */\nexport function vectorDiamondPath(x: number, y: number, r: number = VECTOR_OVERLAY_THEME.widthPointSize): string {\n\treturn `M${fmt(x)} ${fmt(y - r)}L${fmt(x + r)} ${fmt(y)}L${fmt(x)} ${fmt(y + r)}L${fmt(x - r)} ${fmt(y)}Z`;\n}\n\ninterface ModifierEventLike {\n\tshiftKey?: boolean;\n\taltKey?: boolean;\n\tmetaKey?: boolean;\n\tctrlKey?: boolean;\n}\n\n/** Modifiers of a mouse / pointer / key event (`mod` is Cmd or Ctrl). */\nexport function toVectorModifiers(event: ModifierEventLike): { shift: boolean; alt: boolean; mod: boolean } {\n\treturn { shift: !!event.shiftKey, alt: !!event.altKey, mod: !!(event.metaKey || event.ctrlKey) };\n}\n\n/**\n * A pointer event as `VectorEditorController.pointerDown/Move/Up` input: modifiers, button,\n * and the pressure and pointer type the pencil records as a width profile.\n */\nexport function toVectorPointerInput(event: ModifierEventLike & { button?: number; pressure?: number; pointerType?: string }) {\n\treturn {\n\t\t...toVectorModifiers(event),\n\t\tbutton: event.button,\n\t\t...(typeof event.pressure === 'number' ? { pressure: event.pressure } : {}),\n\t\t...(event.pointerType ? { pointerType: event.pointerType } : {})\n\t};\n}\n\n/** A key event as `VectorEditorController.keyDown/keyUp` input. */\nexport function toVectorKeyInput(event: ModifierEventLike & { key: string }) {\n\treturn { key: event.key, ...toVectorModifiers(event) };\n}\n\n/** True for targets that should keep their own keys (inputs, textareas, contenteditable). */\nexport function isVectorTextTarget(target: unknown): boolean {\n\tconst el = target as { tagName?: string; isContentEditable?: boolean; getAttribute?: (name: string) => string | null } | null;\n\tif (!el || typeof el !== 'object') return false;\n\tconst tag = (el.tagName ?? '').toUpperCase();\n\tif (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;\n\treturn !!el.isContentEditable || el.getAttribute?.('contenteditable') === 'true';\n}\n\n// ---------------------------------------------------------------------------\n// Style panel helpers\n// ---------------------------------------------------------------------------\n\n/** Style shown in a style panel: the first selected path's, with `mixed` flags when paths differ. */\nexport interface VectorSelectionStyle {\n\tfill: VectorPaint;\n\tstroke: VectorPaint;\n\tstrokeWidth: number;\n\topacity: number;\n\tstrokeAlign: VectorStrokeAlign;\n\tstartArrow: VectorArrowhead | null;\n\tendArrow: VectorArrowhead | null;\n\tarrowAlign: VectorArrowAlign;\n\tstartArrowScale: number;\n\tendArrowScale: number;\n\t/** The first path's width profile (null when uniform) and the preset it matches. */\n\tstrokeWidthProfile: VectorWidthPoint[] | null;\n\twidthProfile: VectorWidthPreset | 'custom';\n\t/**\n\t * Live corners of the selected anchors (direct tool) or of every corner of the selected\n\t * paths: the first one's radius and kind, and how many corners there are (0: none to round).\n\t */\n\tcornerRadius: number;\n\tcornerKind: VectorCornerKind;\n\tcorners: number;\n\t/** Some selected path is open, so arrowheads apply. */\n\thasOpenPath: boolean;\n\tmixed: {\n\t\tfill: boolean;\n\t\tstroke: boolean;\n\t\tstrokeWidth: boolean;\n\t\topacity: boolean;\n\t\tstrokeAlign: boolean;\n\t\tstartArrow: boolean;\n\t\tendArrow: boolean;\n\t\tarrowAlign: boolean;\n\t\tstartArrowScale: boolean;\n\t\tendArrowScale: boolean;\n\t\twidthProfile: boolean;\n\t\tcornerRadius: boolean;\n\t\tcornerKind: boolean;\n\t};\n\t/** Ids of the paths the style applies to. */\n\tpathIds: string[];\n}\n\nexport function getVectorSelectionStyle(\n\tstate: Pick<VectorEditorState, 'document' | 'selection'> & Partial<Pick<VectorEditorState, 'selectedAnchors' | 'tool'>>\n): VectorSelectionStyle | null {\n\tconst paths: VectorPathNode[] = [];\n\tfor (const id of state.selection) {\n\t\tconst node = findVectorNode(state.document.nodes, id);\n\t\tif (node) paths.push(...collect(node));\n\t}\n\tconst anchorMode = state.tool === 'direct' && !!state.selectedAnchors?.length;\n\tif (anchorMode) {\n\t\tfor (const r of state.selectedAnchors!) {\n\t\t\tif (paths.some((p) => p.id === r.nodeId)) continue;\n\t\t\tconst node = findVectorNode(state.document.nodes, r.nodeId);\n\t\t\tif (node?.type === 'path') paths.push(node);\n\t\t}\n\t}\n\tif (!paths.length) return null;\n\tconst first = paths[0];\n\tconst same = (get: (p: VectorPathNode) => unknown) => paths.every((p) => JSON.stringify(get(p) ?? null) === JSON.stringify(get(first) ?? null));\n\t// Corners: those of the selected anchors in the direct tool, else every corner of the paths.\n\tconst corners: { radius: number; kind: VectorCornerKind }[] = [];\n\tfor (const p of paths) {\n\t\tfor (const c of vectorCorners(p.subpaths)) {\n\t\t\tif (anchorMode && !state.selectedAnchors!.some((r) => r.nodeId === p.id && r.subpath === c.subpath && r.index === c.index)) continue;\n\t\t\tconst corner = p.subpaths[c.subpath].anchors[c.index].corner;\n\t\t\tcorners.push({ radius: corner?.radius ?? 0, kind: corner?.kind ?? 'round' });\n\t\t}\n\t}\n\tconst c0 = corners[0] ?? { radius: 0, kind: 'round' as const };\n\treturn {\n\t\tfill: first.fill ?? null,\n\t\tstroke: first.stroke ?? null,\n\t\tstrokeWidth: first.strokeWidth ?? 1,\n\t\topacity: first.opacity ?? 1,\n\t\tstrokeAlign: first.strokeAlign ?? 'center',\n\t\tstartArrow: first.startArrow ?? null,\n\t\tendArrow: first.endArrow ?? null,\n\t\tarrowAlign: first.arrowAlign ?? 'extend',\n\t\tstartArrowScale: first.startArrowScale ?? 1,\n\t\tendArrowScale: first.endArrowScale ?? 1,\n\t\tstrokeWidthProfile: profileOf(first),\n\t\twidthProfile: widthProfilePresetId(profileOf(first)),\n\t\tcornerRadius: c0.radius,\n\t\tcornerKind: c0.kind,\n\t\tcorners: corners.length,\n\t\thasOpenPath: paths.some((p) => p.subpaths.some((sp) => !sp.closed && sp.anchors.length > 1)),\n\t\tmixed: {\n\t\t\tfill: !same((p) => p.fill),\n\t\t\tstroke: !same((p) => p.stroke),\n\t\t\tstrokeWidth: !same((p) => p.strokeWidth ?? 1),\n\t\t\topacity: !same((p) => p.opacity ?? 1),\n\t\t\tstrokeAlign: !same((p) => p.strokeAlign ?? 'center'),\n\t\t\tstartArrow: !same((p) => p.startArrow ?? null),\n\t\t\tendArrow: !same((p) => p.endArrow ?? null),\n\t\t\tarrowAlign: !same((p) => p.arrowAlign ?? 'extend'),\n\t\t\tstartArrowScale: !same((p) => p.startArrowScale ?? 1),\n\t\t\tendArrowScale: !same((p) => p.endArrowScale ?? 1),\n\t\t\twidthProfile: !same(profileOf),\n\t\t\tcornerRadius: corners.some((c) => c.radius !== c0.radius),\n\t\t\tcornerKind: corners.some((c) => c.kind !== c0.kind)\n\t\t},\n\t\tpathIds: paths.map((p) => p.id)\n\t};\n}\n\nfunction profileOf(p: VectorPathNode): VectorWidthPoint[] | null {\n\tconst points = sanitizeWidthProfile(p.strokeWidthProfile);\n\treturn points.length && widthProfilePresetId(points) !== 'uniform' ? points : null;\n}\n\n/** Labels of the stroke alignments, corner kinds and arrowheads, for style panels. */\nexport const VECTOR_STROKE_ALIGN_LABELS: Readonly<Record<VectorStrokeAlign, string>> = { center: 'Center', inside: 'Inside', outside: 'Outside' };\nexport const VECTOR_CORNER_KIND_LABELS: Readonly<Record<VectorCornerKind, string>> = { round: 'Round', inverted: 'Inverted', chamfer: 'Chamfer' };\nexport const VECTOR_ARROWHEAD_LABELS: Readonly<Record<VectorArrowhead, string>> = {\n\ttriangle: 'Triangle',\n\tarrow: 'Arrow',\n\topen: 'Open arrow',\n\tcircle: 'Circle',\n\tsquare: 'Square',\n\tbar: 'Bar'\n};\n\n/** A paint as `#rrggbb` for `<input type=\"color\">`, or null for none, gradients and other CSS colours. */\nexport function vectorPaintToHex(paint: VectorPaint | undefined): string | null {\n\tif (typeof paint !== 'string') return null;\n\tconst m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(paint.trim());\n\tif (!m) return null;\n\tconst hex = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];\n\treturn `#${hex.toLowerCase()}`;\n}\n\n// ---------------------------------------------------------------------------\n// Toolbar helpers\n// ---------------------------------------------------------------------------\n\n/** A path-editing button of the bundled toolbars. */\nexport interface VectorPathAction {\n\tid: 'join' | 'average' | 'cut' | 'clean' | 'smooth' | 'outline';\n\tlabel: string;\n\ttitle: string;\n\t/** Shortcut after Cmd / Ctrl, if any. */\n\tshortcut?: string;\n\t/** Whether it can run with `selected` nodes and `points` selected anchors (direct tool). */\n\tenabled: (selected: number, points: number) => boolean;\n\trun: (controller: VectorEditorController) => unknown;\n}\n\n/** Join, Average, Cut at Points, Remove Redundant Points, Smooth and Outline Stroke, as the toolbars show them. */\nexport const VECTOR_PATH_ACTIONS: readonly VectorPathAction[] = [\n\t{ id: 'join', label: '⋈', title: 'Join', shortcut: 'J', enabled: (s, p) => s + p > 0, run: (c) => c.joinPaths() },\n\t{ id: 'average', label: '⊹', title: 'Average Points', shortcut: 'Alt+J', enabled: (_s, p) => p > 1, run: (c) => c.averageAnchors('both') },\n\t{ id: 'cut', label: '✂·', title: 'Cut at Points', enabled: (_s, p) => p > 0, run: (c) => c.cutAtAnchors() },\n\t{ id: 'clean', label: '⌁', title: 'Remove Redundant Points', enabled: (s, p) => s + p > 0, run: (c) => c.removeRedundantPoints() },\n\t{ id: 'smooth', label: '∿', title: 'Smooth', enabled: (s, p) => s + p > 0, run: (c) => c.smoothPaths() },\n\t{ id: 'outline', label: '⊡', title: 'Outline Stroke', enabled: (s) => s > 0, run: (c) => c.outlineStroke() }\n];\n\n/** A Pathfinder button of the bundled toolbars. */\nexport interface VectorPathfinderAction {\n\tid: VectorPathfinderOperation;\n\tlabel: string;\n\ttitle: string;\n\t/** Paths (or groups) it needs selected. */\n\tmin: number;\n}\n\n/** Divide, Trim, Merge, Crop, Outline and Minus Back, as the toolbars show them. */\nexport const VECTOR_PATHFINDER_ACTIONS: readonly VectorPathfinderAction[] = [\n\t{ id: 'divide', label: '⊞', title: 'Divide', min: 1 },\n\t{ id: 'trim', label: '⧉', title: 'Trim', min: 2 },\n\t{ id: 'merge', label: '⧈', title: 'Merge', min: 2 },\n\t{ id: 'crop', label: '⌗', title: 'Crop', min: 2 },\n\t{ id: 'outline', label: '⬚', title: 'Outline', min: 1 },\n\t{ id: 'minusBack', label: '⊖', title: 'Minus Back', min: 2 }\n];\n\n/** The on / off path-editing settings the toolbars toggle. */\nexport const VECTOR_EDIT_TOGGLES: readonly { key: 'keepShapeOnDelete' | 'curvatureMoves' | 'recognizeShapes'; label: string; title: string }[] = [\n\t{ key: 'keepShapeOnDelete', label: 'Keep', title: 'Keep shape when deleting points' },\n\t{ key: 'curvatureMoves', label: 'Curv', title: 'Curvature point moves (Alt flips while dragging)' },\n\t{ key: 'recognizeShapes', label: 'Shapes', title: 'Turn pencil scribbles into shapes' }\n];\n\n\n// ---------------------------------------------------------------------------\n// Effects panel\n// ---------------------------------------------------------------------------\n\n/** One effect of the listed stack, with what a panel needs to edit it. */\nexport interface VectorEffectsPanelItem {\n\tid: string;\n\ttype: VectorEffectType;\n\tlabel: string;\n\tenabled: boolean;\n\tparams: Record<string, unknown>;\n\tspecs: readonly VectorEffectParamSpec[];\n}\n\n/** What an Effects panel shows for the selection. */\nexport interface VectorEffectsPanelData {\n\t/** The node whose stack is listed (when one node is selected), else null. */\n\tnodeId: string | null;\n\t/** Selected nodes an added effect goes to. */\n\tcount: number;\n\t/** The listed stack, applied top to bottom. */\n\teffects: VectorEffectsPanelItem[];\n\t/** Something selected is live (Expand Appearance applies). */\n\texpandable: boolean;\n}\n\n/** Effect types in menu order, with their names. */\nexport const VECTOR_EFFECT_MENU: readonly { type: VectorEffectType; label: string }[] = VECTOR_EFFECT_TYPES.map((type) => ({ type, label: VECTOR_EFFECT_LABELS[type] }));\n\n/** The Effects panel of the selection, or null when nothing that takes effects is selected. */\nexport function getVectorEffectsPanel(state: Pick<VectorEditorState, 'document' | 'selection'>): VectorEffectsPanelData | null {\n\tconst nodes = state.selection.map((id) => findVectorNode(state.document.nodes, id)).filter((n): n is VectorNode => !!n && !n.blendStep);\n\tif (!nodes.length) return null;\n\tconst one = nodes.length === 1 ? nodes[0] : null;\n\treturn {\n\t\tnodeId: one?.id ?? null,\n\t\tcount: nodes.length,\n\t\teffects: (one?.effects ?? []).map((e) => ({\n\t\t\tid: e.id,\n\t\t\ttype: e.type,\n\t\t\tlabel: VECTOR_EFFECT_LABELS[e.type] ?? e.type,\n\t\t\tenabled: e.enabled !== false,\n\t\t\tparams: e.params as unknown as Record<string, unknown>,\n\t\t\tspecs: VECTOR_EFFECT_PARAM_SPECS[e.type] ?? []\n\t\t})),\n\t\texpandable: nodes.some(hasVectorAppearance)\n\t};\n}\n","import type { CommandSpec } from '../commands/registry.js';\nimport type { VectorEditorController, VectorShapeInput } from './controller.js';\nimport { vectorNodeBounds, vectorNodeRotation, vectorParentMatrix, vectorPathData, IDENTITY_MATRIX } from './geometry.js';\nimport type { VectorAlign, VectorAnchorRef, VectorArrange, VectorAverageAxis, VectorBooleanOperation, VectorCornerKind, VectorNode, VectorPathfinderOperation, VectorStyle, VectorTool } from './types.js';\nimport { VECTOR_ARROWHEADS, VECTOR_CORNER_KINDS, VECTOR_EFFECT_TYPES, VECTOR_PATHFINDER_OPERATIONS, VECTOR_TOOLS, type VectorEffect, type VectorEffectType, type VectorWidthPoint } from './types.js';\nimport { VECTOR_WIDTH_PRESET_IDS, type VectorTaperOptions, type VectorWaveOptions, type VectorWidthPreset } from './width.js';\nimport { resolveVectorBlendOptions, vectorBlendKeys } from './blendGroup.js';\nimport { VECTOR_BLEND_SPACINGS, type VectorBlendOptions } from './types.js';\n\nexport interface VectorCommandOptions {\n\t/** Id prefix (default `vector.`); use one per editor when several are registered. */\n\tprefix?: string;\n\t/** Palette group (default `Vector`). */\n\tgroup?: string;\n\t/** Command scope. Omit for global commands. */\n\tscope?: string;\n}\n\n/** A node as listed for agents. */\nexport interface VectorNodeListing {\n\tid: string;\n\ttype: 'path' | 'group';\n\tname?: string;\n\tdepth: number;\n\t/** Axis-aligned bounds in document units. */\n\tbounds: { x: number; y: number; width: number; height: number } | null;\n\t/** Rotation in degrees, clockwise (ancestors' rotations included). */\n\trotation: number;\n\tvisible: boolean;\n\tlocked: boolean;\n\tselected: boolean;\n\tfill?: unknown;\n\tstroke?: unknown;\n\tstrokeWidth?: number;\n\t/** SVG path data (paths only). */\n\td?: string;\n\tchildren?: number;\n\t/** A blend group's options (its steps are listed as children with `blendStep`). */\n\tblend?: { spacing: string; steps: number; distance: number; orientToSpine: boolean; linear: boolean; spine: boolean; keys: string[] };\n\t/** An in-between generated by a blend (locked; changes come from its keys). */\n\tblendStep?: boolean;\n\t/** The node's live path effects, applied in order. */\n\teffects?: VectorEffect[];\n\t/** A live boolean group's operation (its children are the operands). */\n\tliveBoolean?: VectorBooleanOperation;\n\t/** An operand's own operation in a live boolean group. */\n\tbooleanOperation?: VectorBooleanOperation;\n}\n\nconst EFFECT_PARAMS_DESCRIPTION =\n\t'Effect parameters (missing ones keep their value or default). trim: start, end (percent), offset (degrees), mode simultaneous|individual. roundCorners: radius. puckerBloat: amount (−100…100). twist: angle, center {x, y} (offset from the middle). zigzag: size, ridges, smooth. wiggle: size, detail, smooth, speed, correlation, phase, seed, time (seconds). offset: amount, join miter|round|bevel, miterLimit, copies, copyOffset. merge: mode merge|add|subtract|intersect|exclude. repeater: copies (fractions fade the last), offset, position {x, y}, anchor {x, y} (offset from the middle), rotation, scale {x, y} (percent), startOpacity, endOpacity (percent), composite below|above.';\n\nconst fail = (message: string): never => {\n\tthrow new Error(message);\n};\n\nconst IDS = { type: 'array', items: { type: 'string' }, description: 'Node ids (default: the selection).' } as const;\n\nconst ANCHORS = {\n\ttype: 'array',\n\titems: {\n\t\ttype: 'object',\n\t\tproperties: { nodeId: { type: 'string' }, subpath: { type: 'integer', minimum: 0 }, index: { type: 'integer', minimum: 0 } },\n\t\trequired: ['nodeId', 'subpath', 'index']\n\t},\n\tdescription: 'Anchors as { nodeId, subpath, index } (default: the selected points).'\n} as const;\n\nconst POINTS = {\n\ttype: 'array',\n\titems: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2 },\n\tminItems: 1,\n\tdescription: 'Polyline [[x, y], …] in document units.'\n} as const;\n\nconst WIDTH_POINTS = {\n\ttype: ['array', 'null'],\n\titems: {\n\t\ttype: 'object',\n\t\tproperties: {\n\t\t\tt: { type: 'number', minimum: 0, maximum: 1, description: 'Position along each subpath by length (0 start, 1 end).' },\n\t\t\tleft: { type: 'number', minimum: 0, description: 'Left side as a multiple of half the stroke width (1 = uniform).' },\n\t\t\tright: { type: 'number', minimum: 0, description: 'Right side (looking along the path, y down).' }\n\t\t},\n\t\trequired: ['t', 'left', 'right']\n\t},\n\tdescription: 'Variable-width profile points, interpolated smoothly (monotone cubic); two points at one `t` make a step. `null` or `[]` strokes at a uniform width.'\n} as const;\n\nconst BLEND_PROPS = {\n\tspacing: { type: 'string', enum: ['steps', 'distance', 'smoothColor'], description: 'How in-betweens are spaced (default steps).' },\n\tsteps: { type: 'integer', minimum: 1, maximum: 1000, description: 'In-betweens between each pair of keys (steps spacing; default 5).' },\n\tdistance: { type: 'number', exclusiveMinimum: 0, description: 'Distance between in-betweens along the spine (distance spacing; default 20).' },\n\torientToSpine: { type: 'boolean', description: 'Turn the in-betweens with a replaced spine.' },\n\tlinear: { type: 'boolean', description: 'Move anchors in straight lines instead of the rotation-aware morph.' }\n} as const;\n\nconst checkBlendOptions = (params: Partial<VectorBlendOptions> | undefined) => {\n\tif (params?.spacing !== undefined && !VECTOR_BLEND_SPACINGS.includes(params.spacing)) fail(`Unknown blend spacing \"${params.spacing}\".`);\n\tif (params?.steps !== undefined && !(Number.isFinite(params.steps) && params.steps >= 1)) fail('steps must be at least 1.');\n\tif (params?.distance !== undefined && !(Number.isFinite(params.distance) && params.distance > 0)) fail('distance must be positive.');\n};\n\nconst PAINT = {\n\tdescription: 'A CSS colour, `null` for none, or a gradient `{ type: \"linear\", angle, stops: [{ offset, color }] }`.'\n} as const;\n\nconst STYLE_PROPS = {\n\tfill: PAINT,\n\tstroke: PAINT,\n\tstrokeWidth: { type: 'number', minimum: 0 },\n\tstrokeLinecap: { type: 'string', enum: ['butt', 'round', 'square'] },\n\tstrokeLinejoin: { type: 'string', enum: ['miter', 'round', 'bevel'] },\n\tstrokeDasharray: { type: 'array', items: { type: 'number' } },\n\tfillRule: { type: 'string', enum: ['nonzero', 'evenodd'] },\n\tstrokeAlign: { type: 'string', enum: ['center', 'inside', 'outside'], description: 'Where the stroke sits on the outline (closed shapes only; open paths stroke centred).' },\n\tstartArrow: { type: ['string', 'null'], enum: [...VECTOR_ARROWHEADS, null], description: 'Head at the start of an open path, or null for none.' },\n\tendArrow: { type: ['string', 'null'], enum: [...VECTOR_ARROWHEADS, null], description: 'Head at the end of an open path, or null for none.' },\n\tarrowAlign: { type: 'string', enum: ['extend', 'tip'], description: '`extend`: the tip sits past the end point; `tip`: on it, the stroke shortened under the head.' },\n\tstartArrowScale: { type: 'number', exclusiveMinimum: 0, description: 'Start head size as a multiple of the stroke width (default 1).' },\n\tendArrowScale: { type: 'number', exclusiveMinimum: 0, description: 'End head size as a multiple of the stroke width (default 1).' },\n\tstrokeWidthProfile: WIDTH_POINTS\n} as const;\n\nconst pickStyle = (params: Record<string, unknown>): VectorStyle => {\n\tconst out: Record<string, unknown> = {};\n\tfor (const key of Object.keys(STYLE_PROPS)) if (params[key] !== undefined) out[key] = params[key];\n\treturn out as VectorStyle;\n};\n\nconst check = (result: { ok: boolean; message?: string }) => {\n\tif (!result.ok) fail(result.message ?? 'The command failed.');\n};\n\n/**\n * Palette and agent commands for a vector editor: list, select, add shapes and paths, style,\n * transform, align, arrange, group, boolean operations and SVG import / export. Register them\n * in a `CommandRegistry` and agents get them through `createCommandToolProvider`. Failures\n * throw sentences, which the registry returns as `{ ok: false, code: 'failed', message }`.\n */\nexport function createVectorCommands(controller: VectorEditorController, options: VectorCommandOptions = {}): CommandSpec[] {\n\tconst prefix = options.prefix ?? 'vector.';\n\tconst group = options.group ?? 'Vector';\n\tconst base = { group, ...(options.scope ? { scope: options.scope } : {}) };\n\tconst hasSelection = () => (controller.getState().selection.length ? true : 'Nothing is selected.');\n\tconst ids = (params: { ids?: string[] } | undefined) => {\n\t\tconst list = params?.ids ?? controller.getState().selection;\n\t\tfor (const id of list) if (!controller.getNode(id)) fail(`There is no node \"${id}\".`);\n\t\tif (!list.length) fail('Nothing is selected; pass ids.');\n\t\treturn list;\n\t};\n\tconst enabledFor = (_: unknown, params: { ids?: string[] } | undefined) => (params?.ids?.length ? true : hasSelection());\n\tconst anchorsOf = (refs: VectorAnchorRef[] | undefined): VectorAnchorRef[] => {\n\t\tif (!refs) return [...controller.getState().selectedAnchors];\n\t\tfor (const r of refs) {\n\t\t\tconst node = controller.getNode(r.nodeId);\n\t\t\tif (node?.type !== 'path' || !node.subpaths[r.subpath]?.anchors[r.index]) fail(`There is no anchor ${r.index} on subpath ${r.subpath} of \"${r.nodeId}\".`);\n\t\t}\n\t\treturn refs;\n\t};\n\n\tconst list = (): { width: number; height: number; nodes: VectorNodeListing[]; selection: readonly string[]; tool: VectorTool } => {\n\t\tconst state = controller.getState();\n\t\tconst out: VectorNodeListing[] = [];\n\t\tconst visit = (nodes: readonly VectorNode[], depth: number) => {\n\t\t\tfor (const n of nodes) {\n\t\t\t\tconst parent = vectorParentMatrix(state.document.nodes, n.id) ?? IDENTITY_MATRIX;\n\t\t\t\tconst b = vectorNodeBounds(n, parent);\n\t\t\t\tout.push({\n\t\t\t\t\tid: n.id,\n\t\t\t\t\ttype: n.type,\n\t\t\t\t\t...(n.name ? { name: n.name } : {}),\n\t\t\t\t\tdepth,\n\t\t\t\t\trotation: Math.round(vectorNodeRotation(n, parent) * 100) / 100,\n\t\t\t\t\tbounds: b && { x: Math.round(b.x * 100) / 100, y: Math.round(b.y * 100) / 100, width: Math.round(b.width * 100) / 100, height: Math.round(b.height * 100) / 100 },\n\t\t\t\t\tvisible: n.visible !== false,\n\t\t\t\t\tlocked: !!n.locked,\n\t\t\t\t\tselected: state.selection.includes(n.id),\n\t\t\t\t\t...(n.type === 'path'\n\t\t\t\t\t\t? { fill: n.fill ?? null, stroke: n.stroke ?? null, strokeWidth: n.strokeWidth ?? 1, d: vectorPathData(n, 2) }\n\t\t\t\t\t\t: { children: n.children.length }),\n\t\t\t\t\t...(n.type === 'group' && n.blend\n\t\t\t\t\t\t? { blend: { ...resolveVectorBlendOptions(n.blend), spine: !!n.blend.spine, keys: vectorBlendKeys(n).map((k) => k.id) } }\n\t\t\t\t\t\t: {}),\n\t\t\t\t\t...(n.blendStep ? { blendStep: true } : {}),\n\t\t\t\t\t...(n.effects?.length ? { effects: n.effects } : {}),\n\t\t\t\t\t...(n.type === 'group' && n.liveBoolean ? { liveBoolean: n.liveBoolean } : {}),\n\t\t\t\t\t...(n.booleanOperation ? { booleanOperation: n.booleanOperation } : {})\n\t\t\t\t});\n\t\t\t\tif (n.type === 'group') visit(n.children, depth + 1);\n\t\t\t}\n\t\t};\n\t\tvisit(state.document.nodes, 0);\n\t\treturn { width: state.document.width, height: state.document.height, nodes: out, selection: state.selection, tool: state.tool };\n\t};\n\n\treturn [\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}list`,\n\t\t\tlabel: 'List Vector Nodes',\n\t\t\treadOnly: true,\n\t\t\tkeywords: ['layers', 'shapes', 'document', 'outline'],\n\t\t\tdescription: 'Lists every node (paths and groups, depth first) with bounds, style, path data and selection.',\n\t\t\trun: list\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}select`,\n\t\t\tlabel: 'Select Nodes',\n\t\t\tkeywords: ['pick', 'choose'],\n\t\t\tdescription: 'Selects nodes by id (empty list clears the selection).',\n\t\t\tparams: { type: 'object', properties: { ids: { type: 'array', items: { type: 'string' } }, add: { type: 'boolean' } }, required: ['ids'] },\n\t\t\trun: (_ctx: unknown, params: { ids: string[]; add?: boolean }) => {\n\t\t\t\tfor (const id of params.ids) if (!controller.getNode(id)) fail(`There is no node \"${id}\".`);\n\t\t\t\tif (!params.ids.length && !params.add) controller.clearSelection();\n\t\t\t\telse controller.select(params.ids, { add: params.add });\n\t\t\t\treturn { selection: controller.getState().selection };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}addShape`,\n\t\t\tlabel: 'Add Shape…',\n\t\t\tkeywords: ['rectangle', 'ellipse', 'circle', 'polygon', 'star', 'line', 'draw'],\n\t\t\tdescription: 'Adds a rectangle, ellipse, polygon, star or line in document units, using the current default style unless overridden.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tkind: { type: 'string', enum: ['rect', 'ellipse', 'polygon', 'star', 'line'] },\n\t\t\t\t\tx: { type: 'number' },\n\t\t\t\t\ty: { type: 'number' },\n\t\t\t\t\twidth: { type: 'number', description: 'For a line: the x offset of its end.' },\n\t\t\t\t\theight: { type: 'number', description: 'For a line: the y offset of its end.' },\n\t\t\t\t\tcornerRadius: { type: 'number', minimum: 0, description: 'Live corner radius of a rect, polygon or star.' },\n\t\t\t\t\tcornerKind: { type: 'string', enum: VECTOR_CORNER_KINDS },\n\t\t\t\t\tsides: { type: 'integer', minimum: 3 },\n\t\t\t\t\tpoints: { type: 'integer', minimum: 3 },\n\t\t\t\t\tinnerRatio: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\tname: { type: 'string' },\n\t\t\t\t\t...STYLE_PROPS\n\t\t\t\t},\n\t\t\t\trequired: ['kind', 'x', 'y', 'width', 'height']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: VectorShapeInput) => ({ id: controller.addShape(params) })\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}addPath`,\n\t\t\tlabel: 'Add Path…',\n\t\t\tkeywords: ['svg', 'path data', 'bezier', 'curve'],\n\t\t\tdescription: 'Adds a path from SVG path data (`d`), in document units.',\n\t\t\tparams: { type: 'object', properties: { d: { type: 'string' }, name: { type: 'string' }, ...STYLE_PROPS }, required: ['d'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { d: string; name?: string } & Record<string, unknown>) => {\n\t\t\t\tconst id = controller.addPath(params.d, { ...pickStyle(params), ...(params.name ? { name: params.name } : {}) });\n\t\t\t\tconst node = controller.getNode(id);\n\t\t\t\tif (node?.type === 'path' && !node.subpaths.length) {\n\t\t\t\t\tcontroller.undo();\n\t\t\t\t\tfail('The path data has no points.');\n\t\t\t\t}\n\t\t\t\treturn { id };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setStyle`,\n\t\t\tlabel: 'Set Style…',\n\t\t\tkeywords: ['fill', 'stroke', 'colour', 'color', 'gradient', 'width'],\n\t\t\tdescription: 'Sets fill, stroke and stroke settings of nodes (groups apply to every path inside).',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, ...STYLE_PROPS } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } & Record<string, unknown>) => {\n\t\t\t\tconst style = pickStyle(params);\n\t\t\t\tif (!Object.keys(style).length) fail('Pass at least one style property.');\n\t\t\t\tcheck(controller.setStyle(style, ids(params)));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setCorners`,\n\t\t\tlabel: 'Corner Radius…',\n\t\t\tkeywords: ['round', 'rounded', 'corners', 'radius', 'fillet', 'chamfer', 'live corners'],\n\t\t\tdescription:\n\t\t\t\t'Sets the live corner radius (and optionally `kind`: round, inverted or chamfer) of `anchors`, else the selected points (direct tool), else every corner of `ids` / the selection. Radius 0 makes corners sharp; radii shrink to fit short sides. The anchors stay editable.',\n\t\t\tparams: { type: 'object', properties: { radius: { type: 'number', minimum: 0 }, kind: { type: 'string', enum: VECTOR_CORNER_KINDS }, ids: IDS, anchors: ANCHORS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { radius?: number; kind?: VectorCornerKind; ids?: string[]; anchors?: VectorAnchorRef[] } | undefined) => {\n\t\t\t\tconst refs = params?.anchors ? anchorsOf(params.anchors) : undefined;\n\t\t\t\tconst target = { refs, ids: params?.ids ? ids(params) : undefined };\n\t\t\t\tif (params?.radius !== undefined) check(controller.setCornerRadius(params.radius, { ...target, kind: params.kind }));\n\t\t\t\telse if (params?.kind) check(controller.setCornerKind(params.kind, target));\n\t\t\t\telse fail('Pass `radius` and / or `kind`.');\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setWidthProfile`,\n\t\t\tlabel: 'Width Profile…',\n\t\t\tkeywords: ['variable width', 'taper', 'tapered', 'pressure', 'profile', 'brush', 'calligraphy', 'bulge', 'thick', 'thin'],\n\t\t\tdescription:\n\t\t\t\t'Gives the stroke of paths a variable width: a `preset` (`uniform` removes the profile), explicit `points` ({ t, left, right }: side scales along each subpath), or a parametric `taper` (startLength / endLength as fractions of the path, startWidth / endWidth 0–1, startEase / endEase 0–1) and / or `wave` (amount 0–1, cycles, phase in degrees). Profiled strokes draw as filled outlines; Outline Stroke bakes them.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tids: IDS,\n\t\t\t\t\tpreset: { type: 'string', enum: VECTOR_WIDTH_PRESET_IDS },\n\t\t\t\t\tpoints: WIDTH_POINTS,\n\t\t\t\t\ttaper: {\n\t\t\t\t\t\ttype: 'object',\n\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\tstartLength: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\t\t\tendLength: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\t\t\tstartWidth: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\t\t\tendWidth: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\t\t\tstartEase: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\t\t\tendEase: { type: 'number', minimum: 0, maximum: 1 }\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\twave: { type: 'object', properties: { amount: { type: 'number', minimum: 0, maximum: 1 }, cycles: { type: 'number', exclusiveMinimum: 0 }, phase: { type: 'number' } } }\n\t\t\t\t}\n\t\t\t},\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (\n\t\t\t\t_ctx: unknown,\n\t\t\t\tparams: { ids?: string[]; preset?: VectorWidthPreset; points?: VectorWidthPoint[] | null; taper?: VectorTaperOptions; wave?: VectorWaveOptions } | undefined\n\t\t\t) => {\n\t\t\t\tconst target = { ids: ids(params) };\n\t\t\t\tif (params?.preset) check(controller.setWidthProfile(params.preset, target));\n\t\t\t\telse if (params?.points !== undefined) check(controller.setWidthProfile(params.points ?? null, target));\n\t\t\t\telse if (params?.taper || params?.wave) check(controller.setWidthProfile({ taper: params.taper, wave: params.wave }, target));\n\t\t\t\telse fail('Pass `preset`, `points`, `taper` or `wave`.');\n\t\t\t\tconst node = controller.getNode(target.ids[0]);\n\t\t\t\treturn { ok: true, points: node?.type === 'path' ? (node.strokeWidthProfile ?? []) : [] };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setWidthPoint`,\n\t\t\tlabel: 'Width Point…',\n\t\t\tkeywords: ['width point', 'variable width', 'stroke width at', 'taper', 'profile'],\n\t\t\tdescription:\n\t\t\t\t'Sets one width point of a path\\'s stroke profile numerically: `index` names an existing point (see `vector.setWidthProfile`\\'s result), leave it out to add one at `t`. `left` / `right` are side widths in document units (how far the stroke reaches on each side), `t` the position along each subpath (0–1); a point moved onto another\\'s `t` makes a step. Returns the profile.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tid: { type: 'string', description: 'The path.' },\n\t\t\t\t\tindex: { type: 'integer', minimum: 0 },\n\t\t\t\t\tt: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\tleft: { type: 'number', minimum: 0 },\n\t\t\t\t\tright: { type: 'number', minimum: 0 }\n\t\t\t\t},\n\t\t\t\trequired: ['id']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { id: string; index?: number; t?: number; left?: number; right?: number } | undefined) => {\n\t\t\t\tif (!params?.id) fail('Pass the path `id`.');\n\t\t\t\tcheck(controller.setWidthPoint({ nodeId: params!.id, index: params!.index ?? null }, { t: params!.t, left: params!.left, right: params!.right }));\n\t\t\t\tconst node = controller.getNode(params!.id);\n\t\t\t\treturn { ok: true, points: node?.type === 'path' ? (node.strokeWidthProfile ?? []) : [] };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}outlineStroke`,\n\t\t\tlabel: 'Outline Stroke',\n\t\t\tkeywords: ['expand', 'stroke to path', 'outline', 'convert stroke'],\n\t\t\tdescription: 'Turns the stroke of paths into filled shapes of the area it paints (alignment, caps, joins, dashes and arrowheads included). Returns the new ids.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tconst created = controller.outlineStroke({ ids: params?.ids ? ids(params) : undefined });\n\t\t\t\tif (!created.length) fail('No selected path has a stroke.');\n\t\t\t\treturn { ids: created };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}transform`,\n\t\t\tlabel: 'Transform…',\n\t\t\tkeywords: ['move', 'translate', 'scale', 'resize', 'rotate', 'flip'],\n\t\t\tdescription:\n\t\t\t\t'Moves (dx, dy), scales (sx, sy, along the selection box axes) and/or rotates (`rotate`: by degrees clockwise around the combined centre; `angle`: to an absolute rotation, each node around its own centre), in that order.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tids: IDS,\n\t\t\t\t\tdx: { type: 'number' },\n\t\t\t\t\tdy: { type: 'number' },\n\t\t\t\t\tsx: { type: 'number' },\n\t\t\t\t\tsy: { type: 'number' },\n\t\t\t\t\trotate: { type: 'number' },\n\t\t\t\t\tangle: { type: 'number' }\n\t\t\t\t}\n\t\t\t},\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[]; dx?: number; dy?: number; sx?: number; sy?: number; rotate?: number; angle?: number }) => {\n\t\t\t\tconst target = ids(params);\n\t\t\t\tif (params.dx || params.dy) check(controller.translate(params.dx ?? 0, params.dy ?? 0, { ids: target }));\n\t\t\t\tif (params.sx !== undefined || params.sy !== undefined) check(controller.scale(params.sx ?? 1, params.sy ?? params.sx ?? 1, { ids: target }));\n\t\t\t\tif (params.rotate) check(controller.rotate(params.rotate, { ids: target }));\n\t\t\t\tif (params.angle !== undefined) check(controller.setRotation(params.angle, { ids: target }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}delete`,\n\t\t\tlabel: 'Delete',\n\t\t\tkeywords: ['remove'],\n\t\t\tdescription: 'Deletes nodes.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tcheck(controller.removeNodes(ids(params)));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}boolean`,\n\t\t\tlabel: 'Boolean Operation…',\n\t\t\tkeywords: ['union', 'unite', 'subtract', 'intersect', 'exclude', 'combine', 'pathfinder'],\n\t\t\tdescription:\n\t\t\t\t'Combines two or more paths with union, subtract (upper shapes from the lowest), intersect or exclude. With `live`, the shapes stay editable operands of a live boolean group instead (a lone live group gets the operation; its operands get their own).',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\toperation: { type: 'string', enum: ['union', 'subtract', 'intersect', 'exclude'] },\n\t\t\t\t\tids: IDS,\n\t\t\t\t\tlive: { type: 'boolean', description: 'Keep the operands editable in a live boolean group (default false: one new path).' }\n\t\t\t\t},\n\t\t\t\trequired: ['operation']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { operation: VectorBooleanOperation; ids?: string[]; live?: boolean }) => {\n\t\t\t\tconst id = controller.booleanOp(params.operation, { ids: ids(params), live: params.live });\n\t\t\t\tif (!id) fail(params.live ? 'Select two or more nodes with one parent, a live boolean group or its operands.' : 'Select at least two overlapping paths (the result was empty).');\n\t\t\t\treturn { id };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}addEffect`,\n\t\t\tlabel: 'Add Path Effect…',\n\t\t\tkeywords: ['effect', 'trim', 'round corners', 'pucker', 'bloat', 'twist', 'zig zag', 'wiggle', 'offset', 'merge', 'repeater', 'appearance', 'live'],\n\t\t\tdescription: 'Adds a live path effect to the bottom of the effect stack of nodes (paths or groups). Effects apply top to bottom; effects after a repeater act on its copies. Returns the new effect ids.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\ttype: { type: 'string', enum: VECTOR_EFFECT_TYPES },\n\t\t\t\t\tparams: { type: 'object', description: EFFECT_PARAMS_DESCRIPTION },\n\t\t\t\t\tindex: { type: 'integer', minimum: 0, description: 'Position in the stack (default: last).' },\n\t\t\t\t\tids: IDS\n\t\t\t\t},\n\t\t\t\trequired: ['type']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\tenabled: enabledFor,\n\t\t\trun: (_ctx: unknown, params: { type: VectorEffectType; params?: Record<string, unknown>; index?: number; ids?: string[] }) => {\n\t\t\t\tif (!VECTOR_EFFECT_TYPES.includes(params.type)) fail(`Unknown effect type \"${params.type}\".`);\n\t\t\t\tconst targets = ids(params);\n\t\t\t\tconst created = controller.addEffect(params.type, { ids: targets, params: params.params, index: params.index });\n\t\t\t\tif (!created.length) fail('Nothing to add the effect to.');\n\t\t\t\treturn { effects: targets.map((id, i) => ({ nodeId: id, effectId: created[i] })) };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}updateEffect`,\n\t\t\tlabel: 'Edit Path Effect…',\n\t\t\tkeywords: ['effect', 'parameters', 'enable', 'disable', 'reorder'],\n\t\t\tdescription: 'Changes a path effect: its parameters, whether it is enabled, and its position in the stack.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tnodeId: { type: 'string' },\n\t\t\t\t\teffectId: { type: 'string' },\n\t\t\t\t\tparams: { type: 'object', description: EFFECT_PARAMS_DESCRIPTION },\n\t\t\t\t\tenabled: { type: 'boolean' },\n\t\t\t\t\tindex: { type: 'integer', minimum: 0, description: 'Move it to this position in the stack (0 applies first).' }\n\t\t\t\t},\n\t\t\t\trequired: ['nodeId', 'effectId']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { nodeId: string; effectId: string; params?: Record<string, unknown>; enabled?: boolean; index?: number }) => {\n\t\t\t\tif (params.params !== undefined || params.enabled !== undefined) check(controller.updateEffect(params.nodeId, params.effectId, { params: params.params, enabled: params.enabled }));\n\t\t\t\tif (params.index !== undefined) check(controller.moveEffect(params.nodeId, params.effectId, params.index));\n\t\t\t\treturn { effects: controller.getNode(params.nodeId)?.effects ?? [] };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}removeEffect`,\n\t\t\tlabel: 'Remove Path Effect',\n\t\t\tkeywords: ['effect', 'delete'],\n\t\t\tdescription: 'Removes a path effect from a node.',\n\t\t\tparams: { type: 'object', properties: { nodeId: { type: 'string' }, effectId: { type: 'string' } }, required: ['nodeId', 'effectId'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { nodeId: string; effectId: string }) => {\n\t\t\t\tcheck(controller.removeEffect(params.nodeId, params.effectId));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}expandAppearance`,\n\t\t\tlabel: 'Expand Appearance',\n\t\t\tkeywords: ['effects', 'bake', 'flatten', 'live boolean', 'expand'],\n\t\t\tdescription: 'Bakes the path effects and live booleans of nodes (and everything inside them) into plain paths.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tundoable: true,\n\t\t\tenabled: enabledFor,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tconst expanded = controller.expandAppearance({ ids: ids(params) });\n\t\t\t\tif (!expanded.length) fail('Nothing selected has path effects or a live boolean.');\n\t\t\t\treturn { ids: expanded };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}blend`,\n\t\t\tlabel: 'Make Blend…',\n\t\t\tkeywords: ['blend', 'morph', 'tween', 'interpolate', 'in-between', 'steps', 'transition'],\n\t\t\tdescription:\n\t\t\t\t'Blends two or more paths or groups (sharing a parent; bottom to top, or in the order given with keepOrder) into a live blend group: the in-betweens morph shape (anchors matched by arc length, the start and winding that twist least, rotation-aware so turning shapes turn) and paint (OKLab colours, gradients, stroke width, opacity, width profiles), and regenerate whenever a key changes. Spacing: `steps` (count between each pair of keys), `distance` (along the spine) or `smoothColor`. `starts` maps key ids to the anchor of their first subpath the blend starts from. Returns the blend id.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tids: IDS,\n\t\t\t\t\t...BLEND_PROPS,\n\t\t\t\t\tkeepOrder: { type: 'boolean', description: 'Blend in the order of `ids` instead of stacking order.' },\n\t\t\t\t\tstarts: { type: 'object', additionalProperties: { type: 'integer', minimum: 0 }, description: 'Start anchor per key id.' }\n\t\t\t\t}\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: (VectorBlendOptions & { ids?: string[]; keepOrder?: boolean; starts?: Record<string, number> }) | undefined) => {\n\t\t\t\tcheckBlendOptions(params);\n\t\t\t\tconst id = controller.blend({ ...params, ids: ids(params) });\n\t\t\t\tif (!id) fail('Select two or more shapes that share a parent.');\n\t\t\t\treturn { id };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}blendOptions`,\n\t\t\tlabel: 'Blend Options…',\n\t\t\tkeywords: ['blend', 'steps', 'spacing', 'distance', 'smooth color', 'orientation', 'align to path'],\n\t\t\tdescription: 'Changes the spacing (steps, distance or smoothColor), step count, distance, orient-to-spine and linear morph of the given (or selected) blends; their steps regenerate.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, ...BLEND_PROPS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: (VectorBlendOptions & { ids?: string[] }) | undefined) => {\n\t\t\t\tcheckBlendOptions(params);\n\t\t\t\tconst r = controller.setBlendOptions(params ?? {}, { ids: params?.ids });\n\t\t\t\tif (!r.ok) fail(r.message);\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}expandBlend`,\n\t\t\tlabel: 'Expand Blend',\n\t\t\tkeywords: ['blend', 'expand', 'flatten', 'bake'],\n\t\t\tdescription: 'Expands the given (or selected) blends into plain groups of their keys and in-betweens (editable paths with fresh ids). Returns the group ids.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tconst groups = controller.expandBlend({ ids: params?.ids });\n\t\t\t\tif (!groups.length) fail('Select a blend to expand.');\n\t\t\t\treturn { ids: groups };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}releaseBlend`,\n\t\t\tlabel: 'Release Blend',\n\t\t\tkeywords: ['blend', 'release', 'undo blend'],\n\t\t\tdescription: 'Releases the given (or selected) blends: the in-betweens go and the keys (plus a replaced spine, as an unpainted path) take the blend\\'s place. Returns their ids.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tconst released = controller.releaseBlend({ ids: params?.ids });\n\t\t\t\tif (!released.length) fail('Select a blend to release.');\n\t\t\t\treturn { ids: released };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}replaceSpine`,\n\t\t\tlabel: 'Replace Spine',\n\t\t\tkeywords: ['blend', 'spine', 'path', 'follow', 'along path'],\n\t\t\tdescription: 'With one blend and one path given (or selected): the path becomes the blend\\'s spine (it is removed) and the keys move onto it, spread evenly by arc length; the in-betweens follow it.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tconst r = controller.replaceBlendSpine({ ids: params?.ids });\n\t\t\t\tif (!r.ok) fail(r.message);\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}pathfinder`,\n\t\t\tlabel: 'Pathfinder…',\n\t\t\tkeywords: ['divide', 'trim', 'merge', 'crop', 'outline', 'minus back', 'split', 'faces'],\n\t\t\tdescription:\n\t\t\t\t'Pathfinder over the given paths (bottom to top): divide (every face its own shape, painted like the top-most shape over it; open unfilled paths cut), trim (hidden parts removed), merge (trim, then touching parts of one fill joined), crop (the top shape crops the others), outline (outlines cut into open strokes where they meet) or minusBack (the top shape minus everything below). Results replace the paths, grouped when there are several.',\n\t\t\tparams: { type: 'object', properties: { operation: { type: 'string', enum: [...VECTOR_PATHFINDER_OPERATIONS] }, ids: IDS }, required: ['operation'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { operation: VectorPathfinderOperation; ids?: string[] }) => {\n\t\t\t\tif (!VECTOR_PATHFINDER_OPERATIONS.includes(params.operation)) fail(`Unknown Pathfinder operation \"${params.operation}\".`);\n\t\t\t\tconst created = controller.pathfinder(params.operation, { ids: ids(params) });\n\t\t\t\tif (!created.length) fail('Nothing came out: select paths that overlap (two or more, except for divide and outline).');\n\t\t\t\treturn { ids: created, selection: [...controller.getState().selection] };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}shapeBuilderFaces`,\n\t\t\tlabel: 'List Shape Builder Faces',\n\t\t\tkeywords: ['shape builder', 'regions', 'faces', 'planar'],\n\t\t\tdescription: 'Lists the faces the given paths divide the plane into: path data, area, a point inside and the ids of the paths filling each (empty when only outlines enclose it).',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => ({ faces: controller.getShapeBuilderFaces({ ids: ids(params) }) })\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}shapeBuilder`,\n\t\t\tlabel: 'Shape Builder',\n\t\t\tkeywords: ['merge faces', 'build shape', 'combine regions', 'delete region'],\n\t\t\tdescription:\n\t\t\t\t'Shape Builder gesture over the faces of the given paths: `points` is the pointer path in document units (one point is a click; [[x, y], …]; use the points from shapeBuilderFaces). Merges the faces it touches into one shape, or with `erase: true` deletes them, the pieces of unfilled open paths and the edges of other outlines (cut where paths meet) within `tolerance`; a deleted edge opens its path there, and edges bounding a deleted face go with it. Shapes covering those faces lose that area. Returns the counts of faces, line pieces and edges.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: { points: POINTS, erase: { type: 'boolean' }, tolerance: { type: 'number', minimum: 0 }, ids: IDS },\n\t\t\t\trequired: ['points']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { points: [number, number][]; erase?: boolean; tolerance?: number; ids?: string[] }) => {\n\t\t\t\tconst result = controller.shapeBuilder(params.points.map(([x, y]) => ({ x, y })), { ids: ids(params), erase: params.erase, tolerance: params.tolerance });\n\t\t\t\tcheck(result);\n\t\t\t\treturn { merged: result.merged, faces: result.faces, lines: result.lines, edges: result.edges };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}fillRegion`,\n\t\t\tlabel: 'Fill Region',\n\t\t\tkeywords: ['paint bucket', 'live paint', 'fill area', 'enclosed'],\n\t\t\tdescription:\n\t\t\t\t'Paint bucket: fills the area the visible outlines enclose around `point` (crossings split, nested shapes left as holes) with a new path in the current fill, below the selection or on top. `gap` closes gaps up to that size between loose ends.',\n\t\t\tparams: { type: 'object', properties: { point: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2, description: '[x, y] in document units.' }, gap: { type: 'number', minimum: 0 } }, required: ['point'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { point: [number, number]; gap?: number }) => {\n\t\t\t\tconst id = controller.fillRegion({ x: params.point[0], y: params.point[1] }, { gap: params.gap });\n\t\t\t\tif (!id) fail('No closed region surrounds that point.');\n\t\t\t\treturn { id };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}align`,\n\t\t\tlabel: 'Align…',\n\t\t\tkeywords: ['left', 'right', 'center', 'top', 'bottom', 'middle'],\n\t\t\tdescription: 'Aligns nodes to their combined bounds, or to the artboard (`to: \"artboard\"`, or when only one node is given).',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: { edge: { type: 'string', enum: ['left', 'center', 'right', 'top', 'middle', 'bottom'] }, to: { type: 'string', enum: ['selection', 'artboard'] }, ids: IDS },\n\t\t\t\trequired: ['edge']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { edge: VectorAlign; to?: 'selection' | 'artboard'; ids?: string[] }) => {\n\t\t\t\tcheck(controller.align(params.edge, { ids: ids(params), to: params.to }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}distribute`,\n\t\t\tlabel: 'Distribute…',\n\t\t\tkeywords: ['space', 'evenly', 'gaps'],\n\t\t\tdescription: 'Spaces three or more nodes with equal gaps.',\n\t\t\tparams: { type: 'object', properties: { direction: { type: 'string', enum: ['horizontal', 'vertical'] }, ids: IDS }, required: ['direction'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { direction: 'horizontal' | 'vertical'; ids?: string[] }) => {\n\t\t\t\tcheck(controller.distribute(params.direction, { ids: ids(params) }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}arrange`,\n\t\t\tlabel: 'Arrange…',\n\t\t\tkeywords: ['order', 'front', 'back', 'forward', 'backward', 'z-index', 'stack'],\n\t\t\tdescription: 'Changes stacking order within each node’s parent.',\n\t\t\tparams: { type: 'object', properties: { order: { type: 'string', enum: ['front', 'forward', 'backward', 'back'] }, ids: IDS }, required: ['order'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { order: VectorArrange; ids?: string[] }) => {\n\t\t\t\tcheck(controller.arrange(params.order, { ids: ids(params) }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}group`,\n\t\t\tlabel: 'Group',\n\t\t\tshortcut: 'Mod+G',\n\t\t\tdescription: 'Groups nodes that share a parent.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, name: { type: 'string' } } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[]; name?: string } | undefined) => {\n\t\t\t\tconst id = controller.group({ ids: ids(params), name: params?.name });\n\t\t\t\tif (!id) fail('Only nodes that share a parent can be grouped.');\n\t\t\t\treturn { id };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}ungroup`,\n\t\t\tlabel: 'Ungroup',\n\t\t\tshortcut: 'Mod+Shift+G',\n\t\t\tdescription: 'Replaces groups with their children.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tcheck(controller.ungroup({ ids: ids(params) }));\n\t\t\t\treturn { selection: controller.getState().selection };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}deleteAnchors`,\n\t\t\tlabel: 'Delete Points',\n\t\t\tkeywords: ['remove', 'anchor', 'point', 'node'],\n\t\t\tdescription:\n\t\t\t\t'Deletes anchors. With `keepShape` (default: the editor setting, on) the neighbouring handles are refitted so the outline keeps its shape; off, the neighbours are joined as they are.',\n\t\t\tparams: { type: 'object', properties: { anchors: ANCHORS, keepShape: { type: 'boolean' } } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { anchors?: VectorAnchorRef[]; keepShape?: boolean } | undefined) => {\n\t\t\t\tcheck(controller.deleteAnchors(anchorsOf(params?.anchors), { keepShape: params?.keepShape }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}moveAnchor`,\n\t\t\tlabel: 'Move Point…',\n\t\t\tkeywords: ['anchor', 'drag', 'curvature'],\n\t\t\tdescription: 'Moves one anchor to (x, y) in document units. `curvature: true` bends its two segments like the Curvature tool instead of carrying its handles along.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: { nodeId: { type: 'string' }, subpath: { type: 'integer', minimum: 0 }, index: { type: 'integer', minimum: 0 }, x: { type: 'number' }, y: { type: 'number' }, curvature: { type: 'boolean' } },\n\t\t\t\trequired: ['nodeId', 'subpath', 'index', 'x', 'y']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: VectorAnchorRef & { x: number; y: number; curvature?: boolean }) => {\n\t\t\t\tcheck(controller.moveAnchor({ nodeId: params.nodeId, subpath: params.subpath, index: params.index }, { x: params.x, y: params.y }, { curvature: params.curvature }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}bendSegment`,\n\t\t\tlabel: 'Bend Segment…',\n\t\t\tkeywords: ['curve', 'segment', 'drag'],\n\t\t\tdescription: 'Bends segment `segment` (from anchor `segment` to the next) so the curve passes through (x, y) at parameter `t` (default 0.5).',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tnodeId: { type: 'string' },\n\t\t\t\t\tsubpath: { type: 'integer', minimum: 0 },\n\t\t\t\t\tsegment: { type: 'integer', minimum: 0 },\n\t\t\t\t\tx: { type: 'number' },\n\t\t\t\t\ty: { type: 'number' },\n\t\t\t\t\tt: { type: 'number', minimum: 0, maximum: 1 }\n\t\t\t\t},\n\t\t\t\trequired: ['nodeId', 'subpath', 'segment', 'x', 'y']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { nodeId: string; subpath: number; segment: number; x: number; y: number; t?: number }) => {\n\t\t\t\tcheck(controller.bendSegment({ nodeId: params.nodeId, subpath: params.subpath, segment: params.segment }, { x: params.x, y: params.y }, params.t));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}join`,\n\t\t\tlabel: 'Join',\n\t\t\tshortcut: 'Mod+J',\n\t\t\tkeywords: ['connect', 'close', 'merge', 'endpoints'],\n\t\t\tdescription: 'Joins the open subpaths of paths nearest endpoints first (ends within `tolerance` merge); a single open path is closed.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, tolerance: { type: 'number', minimum: 0 } } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[]; tolerance?: number } | undefined) => {\n\t\t\t\tcheck(controller.joinPaths({ ids: params?.ids ? ids(params) : undefined, tolerance: params?.tolerance }));\n\t\t\t\treturn { selection: controller.getState().selection };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}average`,\n\t\t\tlabel: 'Average Points…',\n\t\t\tshortcut: 'Mod+Alt+J',\n\t\t\tkeywords: ['align', 'points', 'anchors', 'collapse'],\n\t\t\tdescription: 'Moves anchors onto their average position: the same y (`horizontal`), the same x (`vertical`) or the same point (`both`, default).',\n\t\t\tparams: { type: 'object', properties: { axis: { type: 'string', enum: ['horizontal', 'vertical', 'both'] }, anchors: ANCHORS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { axis?: VectorAverageAxis; anchors?: VectorAnchorRef[] } | undefined) => {\n\t\t\t\tcheck(controller.averageAnchors(params?.axis ?? 'both', anchorsOf(params?.anchors)));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}removeRedundantPoints`,\n\t\t\tlabel: 'Remove Redundant Points',\n\t\t\tkeywords: ['simplify', 'clean up', 'anchors', 'collinear'],\n\t\t\tdescription: 'Drops anchors that can go without moving the outline more than `tolerance` document units (default 0.5).',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, tolerance: { type: 'number', minimum: 0 } } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[]; tolerance?: number } | undefined) => {\n\t\t\t\tcheck(controller.removeRedundantPoints({ ids: params?.ids ? ids(params) : undefined, tolerance: params?.tolerance }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}smooth`,\n\t\t\tlabel: 'Smooth Path',\n\t\t\tkeywords: ['round', 'soften', 'handles', 'curves'],\n\t\t\tdescription: 'Pulls the handles of paths (or of the selected points) towards smooth tangents by `amount` (0–1, default 1).',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, amount: { type: 'number', minimum: 0, maximum: 1 }, anchors: ANCHORS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[]; amount?: number; anchors?: VectorAnchorRef[] } | undefined) => {\n\t\t\t\tcheck(controller.smoothPaths({ ids: params?.ids ? ids(params) : undefined, amount: params?.amount, refs: params?.anchors }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}cut`,\n\t\t\tlabel: 'Cut Path (Scissors)…',\n\t\t\tkeywords: ['scissors', 'split', 'open', 'break'],\n\t\t\tdescription: 'Scissors: cuts path `nodeId` at the outline point nearest (x, y), or cuts at `anchors`. A closed path opens; an open one splits in two.',\n\t\t\tparams: { type: 'object', properties: { nodeId: { type: 'string' }, x: { type: 'number' }, y: { type: 'number' }, anchors: ANCHORS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { nodeId?: string; x?: number; y?: number; anchors?: VectorAnchorRef[] } | undefined) => {\n\t\t\t\tif (params?.nodeId !== undefined) {\n\t\t\t\t\tif (!controller.getNode(params.nodeId)) fail(`There is no node \"${params.nodeId}\".`);\n\t\t\t\t\tif (params.x === undefined || params.y === undefined) fail('Pass x and y with nodeId.');\n\t\t\t\t\tconst created = controller.cutPathAt(params.nodeId, { x: params.x!, y: params.y! });\n\t\t\t\t\tif (!created) fail('Nothing to cut there (the end of an open path, or a locked path).');\n\t\t\t\t\treturn { ids: created };\n\t\t\t\t}\n\t\t\t\tcheck(controller.cutAtAnchors(anchorsOf(params?.anchors)));\n\t\t\t\treturn { ids: controller.getState().selection };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}knife`,\n\t\t\tlabel: 'Knife…',\n\t\t\tkeywords: ['slice', 'cut', 'split', 'divide'],\n\t\t\tdescription: 'Cuts closed paths along a polyline wherever it crosses them edge to edge (no gap between pieces). Cuts `ids`, else the selection, else every path the line crosses.',\n\t\t\tparams: { type: 'object', properties: { points: { ...POINTS, minItems: 2 }, ids: IDS }, required: ['points'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { points: [number, number][]; ids?: string[] }) => {\n\t\t\t\tconst created = controller.knife(params.points.map(([x, y]) => ({ x, y })), { ids: params.ids ? ids(params) : undefined });\n\t\t\t\tif (!created.length) fail('The knife did not cross any closed path from edge to edge.');\n\t\t\t\treturn { ids: created };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}erase`,\n\t\t\tlabel: 'Erase…',\n\t\t\tkeywords: ['eraser', 'rub out', 'remove'],\n\t\t\tdescription:\n\t\t\t\t'Erases a round stroke of diameter `size` (default: the eraser size setting) along a polyline: filled shapes lose the area, outlines are cut under it. Erases from `ids`, else the selection, else every path it reaches.',\n\t\t\tparams: { type: 'object', properties: { points: POINTS, size: { type: 'number', exclusiveMinimum: 0 }, ids: IDS }, required: ['points'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { points: [number, number][]; size?: number; ids?: string[] }) => {\n\t\t\t\tconst changed = controller.erase(params.points.map(([x, y]) => ({ x, y })), { size: params.size, ids: params.ids ? ids(params) : undefined });\n\t\t\t\tif (!changed.length) fail('The eraser did not touch any path.');\n\t\t\t\treturn { ids: changed.filter((id) => controller.getNode(id)) };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setEditing`,\n\t\t\tlabel: 'Path Editing Options…',\n\t\t\tkeywords: ['settings', 'curvature', 'pencil', 'smoothing', 'lazy mouse', 'shapes', 'eraser'],\n\t\t\tdescription:\n\t\t\t\t'Sets how editing behaves: `keepShapeOnDelete`, `curvatureMoves` (anchor drags bend like the Curvature tool), `pencilSmoothing` (lazy mouse, 0–1), `recognizeShapes` (pencil scribbles become lines, rectangles, ellipses, polygons), `eraserSize` and `pencilPressure` (`pen`: record pen pressure on pencil strokes as a width profile, `all`: any pointer reporting pressure, `off`).',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tkeepShapeOnDelete: { type: 'boolean' },\n\t\t\t\t\tcurvatureMoves: { type: 'boolean' },\n\t\t\t\t\tpencilSmoothing: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\trecognizeShapes: { type: 'boolean' },\n\t\t\t\t\teraserSize: { type: 'number', exclusiveMinimum: 0 },\n\t\t\t\t\tpencilPressure: { type: 'string', enum: ['pen', 'all', 'off'] }\n\t\t\t\t}\n\t\t\t},\n\t\t\trun: (_ctx: unknown, params: Record<string, unknown> | undefined) => {\n\t\t\t\tconst patch: Record<string, unknown> = {};\n\t\t\t\tfor (const key of ['keepShapeOnDelete', 'curvatureMoves', 'pencilSmoothing', 'recognizeShapes', 'eraserSize', 'pencilPressure']) if (params?.[key] !== undefined) patch[key] = params[key];\n\t\t\t\tcontroller.setEditing(patch);\n\t\t\t\treturn { editing: controller.getState().editing };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setTool`,\n\t\t\tlabel: 'Choose Tool…',\n\t\t\tkeywords: ['pen', 'pencil', 'select', 'rectangle', 'ellipse'],\n\t\t\tdescription: 'Switches the canvas tool.',\n\t\t\tparams: { type: 'object', properties: { tool: { type: 'string', enum: [...VECTOR_TOOLS] } }, required: ['tool'] },\n\t\t\trun: (_ctx: unknown, params: { tool: VectorTool }) => {\n\t\t\t\tcontroller.setTool(params.tool);\n\t\t\t\treturn { tool: params.tool };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}exportSvg`,\n\t\t\tlabel: 'Export SVG',\n\t\t\treadOnly: true,\n\t\t\tkeywords: ['download', 'save', 'markup'],\n\t\t\tdescription: 'Returns the document as SVG markup.',\n\t\t\tparams: { type: 'object', properties: { precision: { type: 'integer', minimum: 0, maximum: 8 } } },\n\t\t\trun: (_ctx: unknown, params: { precision?: number } | undefined) => ({ svg: controller.toSvg({ precision: params?.precision }) })\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}importSvg`,\n\t\t\tlabel: 'Import SVG…',\n\t\t\tkeywords: ['open', 'load', 'paste', 'markup'],\n\t\t\tdescription: 'Adds the shapes of SVG markup on top of the document (or replaces it with `replace: true`).',\n\t\t\tparams: { type: 'object', properties: { svg: { type: 'string' }, replace: { type: 'boolean' } }, required: ['svg'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { svg: string; replace?: boolean }) => {\n\t\t\t\tcheck(params.replace ? controller.loadSvg(params.svg) : controller.importSvg(params.svg));\n\t\t\t\treturn { nodes: controller.getState().document.nodes.length, selection: controller.getState().selection };\n\t\t\t}\n\t\t}\n\t];\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/vector/index.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/pathEffects.ts","../../src/vector/appearance.ts","../../src/vector/geometry.ts","../../src/vector/gradientStroke.ts","../../src/vector/svg.ts","../../src/vector/gradientTool.ts","../../src/vector/blend.ts","../../src/vector/blendGroup.ts","../../src/vector/snap.ts","../../src/vector/pathEdit.ts","../../src/vector/pathCut.ts","../../src/vector/ink.ts","../../src/vector/builder.ts","../../src/vector/controller.ts","../../src/vector/render.ts","../../src/vector/commands.ts"],"names":["v","at","tangent","plain","s","start","frame","along","mirrored","capped","clean","mul","cross","pt","dist","EPS","len","KAPPA","fmt","out","emit","width","near","left","c","lines","startTangent","length","r","key","node","list"],"mappings":"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IAAA;AACA,IAAA;AACI,MAAA;AACU,MAAA;AACN,MAAA;AACqC,MAAA;AAC/B,MAAA;AACkC,MAAA;AACrC,MAAA;AAC0D,MAAA;AAClB,QAAA;AACF,QAAA;AAChD,MAAA;AACD,IAAA;AACD,EAAA;AACD;AtB8/WyD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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/vector/index.cjs","sourcesContent":[null,"import type { CanvasGradientDef } from '../canvas/gradient.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/** 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}\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}\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/** 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}\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).\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\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];\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) and `repeater`.\n */\nexport type VectorEffectType = 'trim' | 'roundCorners' | 'puckerBloat' | 'twist' | 'zigzag' | 'wiggle' | 'offset' | 'merge' | 'repeater';\nexport const VECTOR_EFFECT_TYPES: readonly VectorEffectType[] = ['trim', 'roundCorners', 'puckerBloat', 'twist', 'zigzag', 'wiggle', 'offset', 'merge', 'repeater'];\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}\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 * 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/** 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 * 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","/**\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 { 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 type {\n\tVec2,\n\tVectorBooleanOperation,\n\tVectorEffect,\n\tVectorEffectParamsMap,\n\tVectorEffectType,\n\tVectorFillRule,\n\tVectorGroupNode,\n\tVectorNode,\n\tVectorPathNode\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};\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};\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};\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\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 '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). */\nfunction childShapes(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 = childShapes(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/**\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\tlet base: VectorAppearanceShape[];\n\tif (node.type === 'path') base = [{ source: node, path: subpathsToBezier(node.subpaths), opacity: 1, order: [] }];\n\telse if (node.liveBoolean) base = liveBooleanShapes(node);\n\telse base = node.children.flatMap((c, i) => childShapes(c, i));\n\tconst out = applyVectorEffects(base, 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...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};\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 {\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): 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 mirror = m[0] * m[3] - m[1] * m[2] < 0 ? -1 : 1;\n\treturn effects.map((e) => {\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`. 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);\n\t\t\tif (want.has(n.id)) {\n\t\t\t\tfor (const id of live) out.add(id);\n\t\t\t\tif (own) 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 { expandVectorCorners, setSubpathCorners } from './corners.js';\nimport { hitTestVectorStroke, vectorStrokeOutlinePath } from './stroke.js';\nimport { mirrorWidthProfile } from './width.js';\nimport { expandVectorAppearance, hasOwnVectorAppearance, transformVectorEffects, vectorAppearanceOutline } from './appearance.js';\nimport type {\n\tVectorAnchor,\n\tVectorAnchorType,\n\tVectorCornerKind,\n\tVectorBounds,\n\tVectorDocument,\n\tVectorGroupNode,\n\tVectorNode,\n\tVectorPaint,\n\tVectorPathNode,\n\tVectorSubpath,\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.\n */\nexport function vectorNodeBounds(node: VectorNode, parent: AffineMatrix = IDENTITY_MATRIX): VectorBounds | null {\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.\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\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\tconst point: Point = [p.x, p.y];\n\tif (hasPaint(node.fill) && pointInBezierPath(path, point, node.fillRule ?? 'nonzero')) return true;\n\tif (hasPaint(node.stroke)) return hitTestVectorStroke(path, p, node, tolerance);\n\treturn distanceToBezierPath(path, point) <= tolerance;\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\nexport const IDENTITY_MATRIX: AffineMatrix = [1, 0, 0, 1, 0, 0];\n\n/** `a` then `b` (apply `a` first). */\nexport function multiplyMatrices(a: AffineMatrix, b: AffineMatrix): AffineMatrix {\n\tconst [a0, a1, a2, a3, a4, a5] = a;\n\tconst [b0, b1, b2, b3, b4, b5] = b;\n\treturn [b0 * a0 + b2 * a1, b1 * a0 + b3 * a1, b0 * a2 + b2 * a3, b1 * a2 + b3 * a3, b0 * a4 + b2 * a5 + b4, b1 * a4 + b3 * a5 + b5];\n}\n\nexport const translateMatrix = (dx: number, dy: number): AffineMatrix => [1, 0, 0, 1, dx, dy];\n\nexport function scaleMatrix(sx: number, sy: number, origin: Vec2 = { x: 0, y: 0 }): AffineMatrix {\n\treturn [sx, 0, 0, sy, origin.x - sx * origin.x, origin.y - sy * origin.y];\n}\n\n/** Rotation by `degrees` clockwise (y points down) around `origin`. */\nexport function rotateMatrix(degrees: number, origin: Vec2 = { x: 0, y: 0 }): AffineMatrix {\n\tconst r = (degrees * Math.PI) / 180;\n\tconst cos = Math.cos(r);\n\tconst sin = Math.sin(r);\n\treturn [cos, sin, -sin, cos, origin.x - cos * origin.x + sin * origin.y, origin.y - sin * origin.x - cos * origin.y];\n}\n\nexport function applyMatrix(m: AffineMatrix, p: Vec2): Vec2 {\n\treturn { x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] };\n}\n\nexport function isIdentityMatrix(m: AffineMatrix, eps = 1e-12): boolean {\n\treturn Math.abs(m[0] - 1) < eps && Math.abs(m[1]) < eps && Math.abs(m[2]) < eps && Math.abs(m[3] - 1) < eps && Math.abs(m[4]) < eps && Math.abs(m[5]) < eps;\n}\n\n/** The inverse matrix (the identity for a singular one). */\nexport function invertMatrix(m: AffineMatrix): AffineMatrix {\n\tconst [a, b, c, d, e, f] = m;\n\tconst det = a * d - b * c;\n\tif (!det || !Number.isFinite(det)) return IDENTITY_MATRIX;\n\treturn [d / det, -b / det, -c / det, a / det, (c * f - d * e) / det, (b * e - a * f) / det];\n}\n\n/** Applies only the linear part (for directions and offsets). */\nexport function applyMatrixLinear(m: AffineMatrix, v: Vec2): Vec2 {\n\treturn { x: m[0] * v.x + m[2] * v.y, y: m[1] * v.x + m[3] * v.y };\n}\n\n/**\n * The rotation of a matrix in degrees, clockwise (y down), in (−180, 180]. For a mirrored\n * matrix it is the rotation left after undoing a horizontal flip, so a flipped rotated shape\n * keeps its angle.\n */\nexport function matrixRotation(m: AffineMatrix): number {\n\tconst [a, b, c, d] = m;\n\tconst mirrored = a * d - b * c < 0;\n\tconst deg = ((mirrored ? Math.atan2(-b - c, d - a) : Math.atan2(b - c, a + d)) * 180) / Math.PI;\n\treturn Math.abs(deg) < 1e-9 ? 0 : deg;\n}\n\n/**\n * Splits `m` into `residual` then `rigid` (`m = residual × rigid` in apply order): `rigid` is\n * the nearest rotation plus the translation, `residual` the linear rest (scale, skew,\n * reflection). An unrotated `m` gives an identity `rigid` and everything in `residual`.\n */\nexport function decomposeRigid(m: AffineMatrix): { rigid: AffineMatrix; residual: AffineMatrix } {\n\tconst [a, b, c, d, e, f] = m;\n\tconst deg = matrixRotation(m);\n\tif (deg === 0) return { rigid: IDENTITY_MATRIX, residual: m };\n\tconst r = (deg * Math.PI) / 180;\n\tconst cos = Math.cos(r);\n\tconst sin = Math.sin(r);\n\treturn {\n\t\trigid: [cos, sin, -sin, cos, e, f],\n\t\tresidual: [cos * a + sin * b, -sin * a + cos * b, cos * c + sin * d, -sin * c + cos * d, 0, 0]\n\t};\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) } 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 mirrored = m[0] * m[3] - m[1] * m[2] < 0 && path.strokeWidthProfile?.length;\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\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 * 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 and `transform`s. Text, images, clip paths, masks, filters and `<use>` are\n * 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 */\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 { hasOwnVectorAppearance, sanitizeVectorEffects, transformVectorEffects, vectorAppearanceNodes } from './appearance.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\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 VectorFillRule,\n\ttype VectorGradient,\n\ttype VectorGradientStop,\n\ttype VectorGroupNode,\n\ttype VectorLineCap,\n\ttype VectorLineJoin,\n\ttype VectorWidthPoint,\n\ttype VectorNode,\n\ttype VectorPaint,\n\ttype VectorBounds,\n\ttype VectorPathNode,\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}\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/** 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\treturn out;\n\t};\n\n\tconst emit = (node: VectorNode, depth: number) => {\n\t\tif (node.visible === false && !options.includeHidden) return;\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, ...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\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 corners = vectorCornersAttribute(node.subpaths, precision);\n\t\tconst live = corners ? ` data-vector-d=\"${bezierPathToSvg(subpathsToBezier(node.subpaths, { corners: false }), { precision })}\" data-vector-corners=\"${corners}\"` : '';\n\t\tconst fillAttrs = ` fill=\"${escapeAttr(fill.value)}\"${node.fillRule === 'evenodd' ? ` fill-rule=\"evenodd\"` : ''}`;\n\t\tconst lineAttrs = (width: number) => {\n\t\t\tlet out = ` stroke=\"${escapeAttr(stroke.value)}\" stroke-width=\"${num(width)}\"`;\n\t\t\tif (node.strokeLinecap && node.strokeLinecap !== 'butt') out += ` stroke-linecap=\"${node.strokeLinecap}\"`;\n\t\t\tif (node.strokeLinejoin && node.strokeLinejoin !== 'miter') out += ` stroke-linejoin=\"${node.strokeLinejoin}\"`;\n\t\t\tif (node.strokeDasharray?.length) out += ` stroke-dasharray=\"${node.strokeDasharray.map(num).join(' ')}\"`;\n\t\t\treturn out;\n\t\t};\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\tif (options.strokes === 'outline') {\n\t\t\tconst area = bezierPathToSvg(vectorStrokeOutlinePath(subpathsToBezier(node.subpaths), node), { precision });\n\t\t\tlines.push(`${indent(depth + 1)}<path data-vector-part=\"stroke-outline\" d=\"${area}\" fill=\"${escapeAttr(stroke.value)}\"/>`);\n\t\t} else {\n\t\t\tlet clip = '';\n\t\t\tif (parts.clip) {\n\t\t\t\tconst rule = node.fillRule === 'evenodd' ? 'evenodd' : 'nonzero';\n\t\t\t\tconst id = escapeAttr(parts.clip.id);\n\t\t\t\tif (parts.clip.kind === 'clip') {\n\t\t\t\t\tdefs.push(`<clipPath id=\"${id}\"><path d=\"${d}\" clip-rule=\"${rule}\"/></clipPath>`);\n\t\t\t\t\tclip = ` clip-path=\"url(#${id})\"`;\n\t\t\t\t} else {\n\t\t\t\t\tconst b = parts.clip.bounds;\n\t\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\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\t\tclip = ` mask=\"url(#${id})\"`;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (parts.outline !== undefined) lines.push(`${indent(depth + 1)}<path data-vector-part=\"stroke-outline\" d=\"${parts.outline}\" fill=\"${escapeAttr(stroke.value)}\"${clip}/>`);\n\t\t\telse lines.push(`${indent(depth + 1)}<path data-vector-part=\"stroke\" d=\"${parts.d}\" fill=\"none\"${lineAttrs(parts.width)}${clip}/>`);\n\t\t\tfor (const h of parts.heads) lines.push(`${indent(depth + 1)}<path data-vector-part=\"head\" d=\"${h}\" fill=\"${escapeAttr(stroke.value)}\"/>`);\n\t\t}\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}\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}\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};\n\nfunction readStyle(el: Element, parent: InheritedStyle): InheritedStyle {\n\tconst out = { ...parent };\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/** 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 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 (hasPaint(next.stroke) && strokeScale > 0 && Math.abs(strokeScale - 1) > 1e-9) next.strokeWidth = (next.strokeWidth ?? 1) * strokeScale;\n\t\treturn next;\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\">): 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 m = multiplyMatrices(parseSvgTransform(child.attrs.transform), matrix);\n\t\t\tconst s = readStyle(child, style);\n\t\t\tconst opacity = child.attrs.opacity !== undefined ? parseFloat(child.attrs.opacity) : undefined;\n\t\t\tconst hidden = child.attrs.display === 'none' || child.attrs.visibility === 'hidden';\n\t\t\tconst meta = {\n\t\t\t\t...(child.attrs['inkscape:label'] || child.attrs['data-name'] ? { name: child.attrs['inkscape:label'] ?? child.attrs['data-name'] } : {}),\n\t\t\t\t...(opacity !== undefined && Number.isFinite(opacity) && opacity < 1 ? { opacity } : {}),\n\t\t\t\t...(hidden ? { visible: false } : {})\n\t\t\t};\n\t\t\t// A blend's in-betweens are regenerated from its keys.\n\t\t\tif (child.attrs['data-vector-blend-step'] !== undefined) continue;\n\t\t\tconst operation = child.attrs['data-vector-boolean-operation'];\n\t\t\tif (BOOLEAN_OPERATIONS.includes(operation as VectorBooleanOperation)) (meta as { booleanOperation?: VectorBooleanOperation }).booleanOperation = operation as VectorBooleanOperation;\n\t\t\tif (child.name === 'g' && child.attrs['data-vector-live'] !== undefined) {\n\t\t\t\tconst node = liveNode(child, s, m, meta);\n\t\t\t\tif (node) out.push(node);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (child.name === 'g' && child.attrs['data-vector-path'] !== undefined) {\n\t\t\t\tconst node = mergedPath(child, s, m, meta);\n\t\t\t\tif (node) out.push(node);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (child.name === 'g' || child.name === 'svg' || child.name === 'a' || child.name === 'switch') {\n\t\t\t\tconst children = visit(child, s, m);\n\t\t\t\tif (!children.length) continue;\n\t\t\t\tconst group: VectorGroupNode = { id: idFor(child), type: 'group', ...meta, children };\n\t\t\t\tconst blend = child.name === 'g' ? blendFromAttribute(child.attrs['data-vector-blend'], m) : null;\n\t\t\t\tif (blend) group.blend = blend;\n\t\t\t\tout.push(group);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst subpaths = shapeOf(child);\n\t\t\tif (!subpaths || !subpaths.length) continue;\n\t\t\tconst node = placed(\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\tm\n\t\t\t);\n\t\t\tout.push(node);\n\t\t}\n\t\treturn out;\n\t};\n\n\tconst nodes = visit(root, readStyle(root, { fill: '#000', stroke: 'none', strokeWidth: '' }), rootMatrix);\n\treturn { width, height, nodes };\n}\n","/**\n * The Gradient tool's on-canvas annotator: a bar from the start (or centre) handle to the end (or\n * radius) handle with the colour stops under it and midpoint diamonds above it, plus a radial,\n * conic or diamond gradient's extent outline, aspect handle and (radial) focal point. All in\n * screen pixels, for hit-testing and the overlay.\n *\n * After vectorcraft `crates/tools/src/xform/gradient.rs` (`Annotator`: stop chips below the bar,\n * midpoints above it, a stop dragged off the bar is removed, a click on the bar adds one), ArtCraft,\n * MIT OR Apache-2.0 — see THIRD_PARTY_NOTICES.md.\n */\nimport { collectPathNodes, findVectorNode, vectorWorldMatrix, IDENTITY_MATRIX, multiplyMatrices, type AffineMatrix } from './geometry.js';\nimport { vectorPathLocalBounds } from './svg.js';\nimport {\n\tisCentredVectorGradient,\n\tisVectorGradient,\n\tsortedVectorGradientStops,\n\tvectorGradientHandles,\n\tvectorGradientMatrix,\n\tvectorGradientMidpointPosition,\n\ttype VectorGradientHandle\n} from './gradient.js';\nimport type { Vec2, VectorBounds, VectorDocument, VectorGradient, VectorGradientStop, VectorPathNode, VectorViewport } from './types.js';\n\n/** Which paint of the selection the Gradient tool and the gradient editor work on. */\nexport type VectorGradientTarget = 'fill' | 'stroke';\n\n/** A part of the annotator under the pointer. */\nexport type VectorGradientPart =\n\t| { kind: 'handle'; handle: VectorGradientHandle }\n\t| { kind: 'stop'; index: number }\n\t| { kind: 'midpoint'; index: number }\n\t/** The bar at `offset` (0–1): a click adds a stop there, a drag moves the gradient. */\n\t| { kind: 'bar'; offset: number };\n\n/** Distance of the stop chips below the bar, the midpoint diamonds above it (screen pixels). */\nexport const VECTOR_GRADIENT_STOP_GAP = 12;\nexport const VECTOR_GRADIENT_MIDPOINT_GAP = 9;\n/** How far off the bar a dragged stop goes before it is removed (screen pixels). */\nexport const VECTOR_GRADIENT_OFF_BAR = 24;\nconst STOP_HIT = 6;\nconst MIDPOINT_HIT = 5;\nconst HANDLE_HIT = 6;\nconst BAR_HIT = 5;\n\nexport interface VectorGradientAnnotator {\n\tnodeId: string;\n\ttarget: VectorGradientTarget;\n\tgradient: VectorGradient;\n\t/** The path's box in its own space. */\n\tbounds: VectorBounds | null;\n\t/** The path's own space → document. */\n\tworld: AffineMatrix;\n\t/** Screen positions of the handles. */\n\tstart: Vec2;\n\tend: Vec2;\n\t/** Radial, conic and diamond gradients. */\n\taspect: Vec2 | null;\n\t/** Radial gradients. */\n\tfocal: Vec2 | null;\n\t/** Unit normal to the bar towards the stop chips (screen). */\n\tnormal: Vec2;\n\t/** Stops (sorted, as the gradient stores them) with their chips' screen positions. */\n\tstops: (VectorGradientStop & { index: number; x: number; y: number })[];\n\t/** The midpoint diamond after each stop but the last (screen). */\n\tmidpoints: { index: number; x: number; y: number; midpoint: number }[];\n\t/** Screen path of the extent: an ellipse (radial, conic) or a rhombus (diamond). */\n\textent: string | null;\n}\n\nconst fmt = (n: number) => Math.round(n * 100) / 100;\nconst apply = (m: AffineMatrix, 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] });\n\n/** The path whose `target` paint the annotator shows: the first path of the selection with a gradient there. */\nexport function vectorGradientTargetPath(doc: VectorDocument, selection: readonly string[], target: VectorGradientTarget): VectorPathNode | null {\n\tfor (const id of selection) {\n\t\tconst node = findVectorNode(doc.nodes, id);\n\t\tif (!node) continue;\n\t\tconst path = collectPathNodes(node).find((p) => isVectorGradient(p[target]));\n\t\tif (path) return path;\n\t}\n\treturn null;\n}\n\n/** The annotator of the selection's gradient (`target` paint), in screen pixels, or null when it has none. */\nexport function getVectorGradientAnnotator(\n\tdoc: VectorDocument,\n\tselection: readonly string[],\n\tviewport: VectorViewport,\n\ttarget: VectorGradientTarget\n): VectorGradientAnnotator | null {\n\tconst path = vectorGradientTargetPath(doc, selection, target);\n\tif (!path) return null;\n\tconst gradient = path[target] as VectorGradient;\n\tconst world = vectorWorldMatrix(doc.nodes, path.id) ?? IDENTITY_MATRIX;\n\tconst screen = multiplyMatrices(world, [viewport.zoom, 0, 0, viewport.zoom, viewport.x, viewport.y]);\n\tconst bounds = vectorPathLocalBounds(path);\n\tconst h = vectorGradientHandles(gradient, bounds, screen);\n\tconst v = { x: h.end.x - h.start.x, y: h.end.y - h.start.y };\n\tconst len = Math.hypot(v.x, v.y);\n\tconst normal = len > 1e-9 ? { x: -v.y / len, y: v.x / len } : { x: 0, y: 1 };\n\tconst at = (t: number, gap: number) => ({ x: h.start.x + v.x * t + normal.x * gap, y: h.start.y + v.y * t + normal.y * gap });\n\tconst sorted = sortedVectorGradientStops(gradient);\n\tconst stops = sorted.map((s, index) => ({ ...s, index, ...at(s.offset, VECTOR_GRADIENT_STOP_GAP) }));\n\tconst midpoints = sorted.slice(0, -1).flatMap((s, index) => {\n\t\tconst pos = vectorGradientMidpointPosition(sorted, index);\n\t\treturn pos === null || sorted[index + 1].offset - s.offset < 1e-6 ? [] : [{ index, midpoint: s.midpoint ?? 0.5, ...at(pos, -VECTOR_GRADIENT_MIDPOINT_GAP) }];\n\t});\n\tlet extent: string | null = null;\n\tif (isCentredVectorGradient(gradient.type)) {\n\t\tconst frame = multiplyMatrices(vectorGradientMatrix(gradient, bounds), screen);\n\t\tconst pts =\n\t\t\tgradient.type === 'diamond'\n\t\t\t\t? [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 0 }, { x: 0, y: -1 }]\n\t\t\t\t: Array.from({ length: 48 }, (_, k) => ({ x: Math.cos((k * Math.PI) / 24), y: Math.sin((k * Math.PI) / 24) }));\n\t\textent = `M${pts.map((p) => apply(frame, p)).map((p) => `${fmt(p.x)} ${fmt(p.y)}`).join('L')}Z`;\n\t}\n\treturn { nodeId: path.id, target, gradient, bounds, world, start: h.start, end: h.end, aspect: h.aspect, focal: h.focal, normal, stops, midpoints, extent };\n}\n\n/** Where `p` lies relative to the bar: offset along it (0 at the start, 1 at the end) and distance towards the stop chips. */\nexport function vectorGradientBarPosition(a: Pick<VectorGradientAnnotator, 'start' | 'end' | 'normal'>, p: Vec2): { offset: number; across: number } {\n\tconst v = { x: a.end.x - a.start.x, y: a.end.y - a.start.y };\n\tconst l2 = v.x * v.x + v.y * v.y;\n\tconst d = { x: p.x - a.start.x, y: p.y - a.start.y };\n\tif (l2 < 1e-12) return { offset: 0, across: Math.hypot(d.x, d.y) };\n\treturn { offset: (d.x * v.x + d.y * v.y) / l2, across: d.x * a.normal.x + d.y * a.normal.y };\n}\n\n/** The annotator part under screen point `p`: stops, then midpoints, the handles and the bar. */\nexport function hitVectorGradientAnnotator(a: VectorGradientAnnotator, p: Vec2): VectorGradientPart | null {\n\tconst near = (q: Vec2 | null, r: number) => !!q && Math.hypot(q.x - p.x, q.y - p.y) <= r;\n\tfor (let i = a.stops.length - 1; i >= 0; i--) if (near(a.stops[i], STOP_HIT)) return { kind: 'stop', index: a.stops[i].index };\n\tfor (const m of a.midpoints) if (near(m, MIDPOINT_HIT)) return { kind: 'midpoint', index: m.index };\n\tconst centred = isCentredVectorGradient(a.gradient.type);\n\tif (near(a.end, HANDLE_HIT)) return { kind: 'handle', handle: centred ? 'radius' : 'end' };\n\t// The focal point sits on the centre until moved: grab it with Alt there (see the controller).\n\tif (a.focal && near(a.focal, HANDLE_HIT) && !near(a.start, 1)) return { kind: 'handle', handle: 'focal' };\n\tif (near(a.start, HANDLE_HIT)) return { kind: 'handle', handle: 'start' };\n\tif (near(a.aspect, HANDLE_HIT)) return { kind: 'handle', handle: 'aspect' };\n\tconst { offset, across } = vectorGradientBarPosition(a, p);\n\tif (offset >= 0 && offset <= 1 && across >= -BAR_HIT && across <= VECTOR_GRADIENT_STOP_GAP + STOP_HIT) return { kind: 'bar', offset };\n\treturn null;\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","// Portions ported from vectorcraft-main/crates/doc/src/blend.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n/**\n * Live blend groups: a group whose `blend` is set keeps its key shapes as children and\n * regenerates the in-betweens (`blendStep` children, locked) whenever a key or the options\n * change. Steps follow the straight lines between the keys' centres, or a replaced spine.\n *\n * - **Spacing** (`vectorBlendStepCount`): a number of steps per pair of keys, a fixed distance\n * along the spine, or smooth colour — one step per two levels of the largest colour channel\n * difference, by distance when the colours match.\n * - **Spine**: with a replaced spine the keys sit along it, spread evenly by arc length; each\n * step is moved by how far the spine bends away from the chord between its keys, so a step\n * lands on the spine and the keys stay where they are. Orient to spine also turns the steps\n * by how much the spine's direction differs from the keys' (keys keep their orientation);\n * without a replaced spine the steps run straight and keep their orientation.\n */\nimport type { Point } from '../canvas/types.js';\nimport { hexToRgba } from '../tokens/color.js';\nimport { segmentPoint, segmentTangent } from '../canvas/pathops/bezier.js';\nimport { createVectorMorph, type VectorMorph } from './blend.js';\nimport {\n\tcollectPathNodes,\n\tIDENTITY_MATRIX,\n\trotateMatrix,\n\tsubpathSegment,\n\tsubpathSegmentCount,\n\ttransformVectorNode,\n\ttranslateMatrix,\n\tmultiplyMatrices,\n\tnodeMatrix,\n\tvectorNodeBounds,\n\ttype AffineMatrix\n} from './geometry.js';\nimport type { VectorBlend, VectorBlendOptions, VectorGroupNode, VectorNode, VectorPaint, VectorPathNode, VectorSubpath } from './types.js';\n\nexport const DEFAULT_VECTOR_BLEND_STEPS = 5;\nexport const DEFAULT_VECTOR_BLEND_DISTANCE = 20;\n/** Most steps between two keys. */\nexport const VECTOR_BLEND_MAX_STEPS = 1000;\n/** Most steps smooth colour makes between two keys. */\nexport const VECTOR_BLEND_MAX_SMOOTH_STEPS = 256;\n\n/** Whether a node is a live blend group. */\nexport function isVectorBlendGroup(node: VectorNode | null | undefined): node is VectorGroupNode & { blend: VectorBlend } {\n\treturn !!node && node.type === 'group' && !!node.blend;\n}\n\n/** The keys of a blend group (its children that are not generated steps), in stacking order. */\nexport function vectorBlendKeys(group: VectorGroupNode): VectorNode[] {\n\treturn group.children.filter((c) => !c.blendStep);\n}\n\n/** The full options of a blend, defaults filled in. */\nexport function resolveVectorBlendOptions(options: VectorBlendOptions | null | undefined): Required<VectorBlendOptions> {\n\treturn {\n\t\tspacing: options?.spacing ?? 'steps',\n\t\tsteps: options?.steps ?? DEFAULT_VECTOR_BLEND_STEPS,\n\t\tdistance: options?.distance ?? DEFAULT_VECTOR_BLEND_DISTANCE,\n\t\torientToSpine: options?.orientToSpine ?? false,\n\t\tlinear: options?.linear ?? false\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Step counts\n// ---------------------------------------------------------------------------\n\nfunction paintColors(p: VectorPaint | undefined): string[] {\n\tif (typeof p === 'string') return [p];\n\tif (p && typeof p === 'object') return p.stops.map((s) => s.color);\n\treturn [];\n}\n\n/** Largest channel difference (0–1) between the colours of two shapes' fills and strokes. */\nexport function vectorBlendColorDistance(a: VectorNode, b: VectorNode): number {\n\tconst pa = collectPathNodes(a)[0];\n\tconst pb = collectPathNodes(b)[0];\n\tif (!pa || !pb) return 0;\n\tlet d = 0;\n\tfor (const key of ['fill', 'stroke'] as const) {\n\t\tconst ca = paintColors(pa[key]);\n\t\tconst cb = paintColors(pb[key]);\n\t\tca.forEach((c, i) => {\n\t\t\tconst other = cb[i] ?? cb[0];\n\t\t\tif (other === undefined) return;\n\t\t\tconst x = hexToRgba(c);\n\t\t\tconst y = hexToRgba(other);\n\t\t\tif (!x || !y) return;\n\t\t\td = Math.max(d, Math.abs(x.r - y.r), Math.abs(x.g - y.g), Math.abs(x.b - y.b), Math.abs(x.a - y.a));\n\t\t});\n\t}\n\treturn d;\n}\n\nconst centreOf = (n: VectorNode): Point => {\n\tconst b = vectorNodeBounds(n);\n\treturn b ? [b.x + b.width / 2, b.y + b.height / 2] : [0, 0];\n};\n\n/** Number of in-betweens between two keys `length` apart along the spine. */\nexport function vectorBlendStepCount(a: VectorNode, b: VectorNode, options: VectorBlendOptions | null | undefined, length: number): number {\n\tconst o = resolveVectorBlendOptions(options);\n\tif (o.spacing === 'distance') {\n\t\tconst d = Math.max(0.01, o.distance);\n\t\treturn Math.min(VECTOR_BLEND_MAX_STEPS, Math.max(0, Math.round(length / d) - 1));\n\t}\n\tif (o.spacing === 'smoothColor') {\n\t\tconst cd = vectorBlendColorDistance(a, b);\n\t\tif (cd > 1 / 255) return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil((cd * 255) / 2)));\n\t\t// Same colours: base the count on how far the shapes are apart.\n\t\tconst ba = vectorNodeBounds(a);\n\t\tconst bb = vectorNodeBounds(b);\n\t\tconst dist =\n\t\t\tba && bb\n\t\t\t\t? Math.max(Math.abs(ba.x - bb.x), Math.abs(ba.y - bb.y), Math.abs(ba.x + ba.width - bb.x - bb.width), Math.abs(ba.y + ba.height - bb.y - bb.height))\n\t\t\t\t: length;\n\t\treturn Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(dist / 2)));\n\t}\n\treturn Math.min(VECTOR_BLEND_MAX_STEPS, Math.max(1, Math.round(Number.isFinite(o.steps) ? o.steps : DEFAULT_VECTOR_BLEND_STEPS)));\n}\n\n// ---------------------------------------------------------------------------\n// Spine\n// ---------------------------------------------------------------------------\n\n/** A subpath flattened to a polyline with cumulative arc lengths and tangent angles. */\nexport interface VectorBlendRail {\n\tpts: Point[];\n\tcum: number[];\n\tangles: number[];\n\ttotal: number;\n\tclosed: boolean;\n}\n\n/** Flattens a spine subpath (32 samples per curve). Null with fewer than two distinct points. */\nexport function vectorBlendRail(sp: VectorSubpath): VectorBlendRail | null {\n\tconst pts: Point[] = [];\n\tconst angles: number[] = [];\n\tconst push = (p: Point, tangent: Point) => {\n\t\tconst last = pts[pts.length - 1];\n\t\tif (last && Math.hypot(p[0] - last[0], p[1] - last[1]) < 1e-9) return;\n\t\tpts.push(p);\n\t\tangles.push(Math.atan2(tangent[1], tangent[0]));\n\t};\n\tconst count = subpathSegmentCount(sp);\n\tfor (let i = 0; i < count; i++) {\n\t\tconst seg = subpathSegment(sp, i);\n\t\tconst samples = seg.length === 2 ? 1 : 32;\n\t\tif (i === 0) push(seg[0], segmentTangent(seg, 0));\n\t\tfor (let k = 1; k <= samples; k++) push(segmentPoint(seg, k / samples), segmentTangent(seg, k / samples));\n\t}\n\tif (pts.length < 2) return null;\n\tconst cum = [0];\n\tfor (let i = 1; i < pts.length; i++) cum.push(cum[i - 1] + Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]));\n\treturn { pts, cum, angles, total: cum[cum.length - 1], closed: sp.closed };\n}\n\n/** Point and direction (radians) at arc length `s` (clamped; a closed rail wraps). */\nexport function vectorBlendRailAt(rail: VectorBlendRail, s: number): { point: Point; angle: number } {\n\tlet len = Number.isFinite(s) ? s : 0;\n\tif (rail.closed && rail.total > 0) len = ((len % rail.total) + rail.total) % rail.total;\n\tlen = Math.min(rail.total, Math.max(0, len));\n\tlet lo = 0;\n\tlet hi = rail.cum.length - 1;\n\twhile (hi - lo > 1) {\n\t\tconst mid = (lo + hi) >> 1;\n\t\tif (rail.cum[mid] <= len) lo = mid;\n\t\telse hi = mid;\n\t}\n\tconst span = rail.cum[hi] - rail.cum[lo];\n\tconst u = span > 1e-12 ? (len - rail.cum[lo]) / span : 0;\n\tconst a = rail.pts[lo];\n\tconst b = rail.pts[hi];\n\treturn { point: [a[0] + (b[0] - a[0]) * u, a[1] + (b[1] - a[1]) * u], angle: Math.atan2(b[1] - a[1], b[0] - a[0]) };\n}\n\n/**\n * The spine of a blend in the group's space and where along it each key sits: the replaced\n * spine (keys spread evenly by arc length) or the straight lines between the keys' centres.\n */\nexport function vectorBlendSpine(group: VectorGroupNode): { spine: VectorSubpath; rail: VectorBlendRail; stops: number[]; replaced: boolean } | null {\n\tconst keys = vectorBlendKeys(group);\n\tif (keys.length < 2) return null;\n\tconst stored = group.blend?.spine;\n\tif (stored && stored.anchors.length >= 2) {\n\t\tconst rail = vectorBlendRail(stored);\n\t\tif (rail) {\n\t\t\tconst end = rail.closed ? rail.total * (1 - 1 / keys.length) : rail.total;\n\t\t\treturn { spine: stored, rail, stops: keys.map((_, i) => (end * i) / (keys.length - 1)), replaced: true };\n\t\t}\n\t}\n\tconst centres = keys.map(centreOf);\n\tconst spine: VectorSubpath = { closed: false, anchors: centres.map((c) => ({ x: c[0], y: c[1] })) };\n\tconst stops = [0];\n\tfor (let i = 1; i < centres.length; i++) stops.push(stops[i - 1] + Math.hypot(centres[i][0] - centres[i - 1][0], centres[i][1] - centres[i - 1][1]));\n\tconst rail = vectorBlendRail(spine) ?? { pts: centres, cum: stops, angles: centres.map(() => 0), total: stops[stops.length - 1], closed: false };\n\treturn { spine, rail, stops, replaced: false };\n}\n\n/** Where key `i` of a blend with spine `spine` should sit: its stop on the spine. */\nexport function vectorBlendKeyTargets(group: VectorGroupNode): Point[] {\n\tconst s = vectorBlendSpine(group);\n\treturn s ? s.stops.map((stop) => vectorBlendRailAt(s.rail, stop).point) : [];\n}\n\n// ---------------------------------------------------------------------------\n// Generating steps\n// ---------------------------------------------------------------------------\n\nconst morphCache = new WeakMap<VectorNode, WeakMap<VectorNode, Map<string, VectorMorph>>>();\n\nfunction morphFor(a: VectorNode, b: VectorNode, blend: VectorBlend): VectorMorph {\n\tconst starts = [blend.starts?.[a.id] ?? null, blend.starts?.[b.id] ?? null] as const;\n\tconst key = JSON.stringify([!!blend.linear, starts]);\n\tlet inner = morphCache.get(a);\n\tif (!inner) morphCache.set(a, (inner = new WeakMap()));\n\tlet byKey = inner.get(b);\n\tif (!byKey) inner.set(b, (byKey = new Map()));\n\tlet morph = byKey.get(key);\n\tif (!morph) byKey.set(key, (morph = createVectorMorph(a, b, { linear: blend.linear, starts })));\n\treturn morph;\n}\n\nconst wrapAngle = (a: number) => Math.atan2(Math.sin(a), Math.cos(a));\n\n/** A step marked as generated: locked, flagged, ids derived from `id`. */\nfunction asStep(node: VectorNode, id: string): VectorNode {\n\tconst rename = (n: VectorNode, nid: string): VectorNode =>\n\t\tn.type === 'group' ? { ...n, id: nid, children: n.children.map((c, i) => rename(c, `${nid}.${i}`)) } : { ...n, id: nid };\n\treturn { ...rename(node, id), blendStep: true, locked: true };\n}\n\n/**\n * A blend group's children with fresh steps: each key followed by the in-betweens to the next,\n * bottom to top. Groups with fewer than two keys keep just their keys.\n */\nexport function generateVectorBlendChildren(group: VectorGroupNode): VectorNode[] {\n\tconst keys = vectorBlendKeys(group);\n\tconst blend = group.blend;\n\tif (!blend || keys.length < 2) return keys;\n\tconst spine = vectorBlendSpine(group);\n\tif (!spine) return keys;\n\tconst out: VectorNode[] = [];\n\tlet counter = 0;\n\tkeys.forEach((a, i) => {\n\t\tout.push(a);\n\t\tconst b = keys[i + 1];\n\t\tif (!b) return;\n\t\tconst s0 = spine.stops[i];\n\t\tconst s1 = spine.stops[i + 1];\n\t\tconst n = vectorBlendStepCount(a, b, blend, Math.abs(s1 - s0));\n\t\tconst morph = morphFor(a, b, blend);\n\t\tconst p0 = vectorBlendRailAt(spine.rail, s0);\n\t\tconst p1 = vectorBlendRailAt(spine.rail, s1);\n\t\tfor (let j = 1; j <= n; j++) {\n\t\t\tconst t = j / (n + 1);\n\t\t\tlet step = morph.at(t);\n\t\t\tif (spine.replaced) {\n\t\t\t\tconst p = vectorBlendRailAt(spine.rail, s0 + (s1 - s0) * t);\n\t\t\t\tconst dx = p.point[0] - (p0.point[0] + (p1.point[0] - p0.point[0]) * t);\n\t\t\t\tconst dy = p.point[1] - (p0.point[1] + (p1.point[1] - p0.point[1]) * t);\n\t\t\t\tlet m: AffineMatrix = Math.hypot(dx, dy) > 1e-9 ? translateMatrix(dx, dy) : IDENTITY_MATRIX;\n\t\t\t\tif (blend.orientToSpine) {\n\t\t\t\t\tconst turn = wrapAngle(p.angle - (p0.angle + wrapAngle(p1.angle - p0.angle) * t));\n\t\t\t\t\tif (Math.abs(turn) > 1e-9) {\n\t\t\t\t\t\tconst c = centreOf(step);\n\t\t\t\t\t\tconst origin = { x: c[0] + m[4], y: c[1] + m[5] };\n\t\t\t\t\t\tm = multiplyMatrices(m, rotateMatrix((turn * 180) / Math.PI, origin));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (m !== IDENTITY_MATRIX) step = transformVectorNode(step, m);\n\t\t\t}\n\t\t\tout.push(asStep(step, `${group.id}~${++counter}`));\n\t\t}\n\t});\n\treturn out;\n}\n\nconst childrenCache = new WeakMap<VectorNode, { sig: string; keys: VectorNode[]; children: VectorNode[] }>();\n\nconst sameList = (a: readonly VectorNode[], b: readonly VectorNode[]) => a.length === b.length && a.every((n, i) => n === b[i]);\n\nfunction blendChildren(group: VectorGroupNode): VectorNode[] {\n\tconst keys = vectorBlendKeys(group);\n\tconst sig = `${group.id}|${JSON.stringify(group.blend)}`;\n\tconst cached = keys[0] ? childrenCache.get(keys[0]) : undefined;\n\tlet children: VectorNode[];\n\tif (cached && cached.sig === sig && sameList(cached.keys, keys)) children = cached.children;\n\telse {\n\t\tchildren = generateVectorBlendChildren(group);\n\t\tif (keys[0]) childrenCache.set(keys[0], { sig, keys, children });\n\t}\n\treturn sameList(children, group.children) ? group.children : children;\n}\n\n/**\n * Nodes with every blend group's steps brought up to date (nested blends first). Returns the\n * same array when nothing changed; steps are cached per set of keys, so calling this after\n * every edit is cheap.\n */\nexport function refreshVectorBlends(nodes: readonly VectorNode[]): VectorNode[] {\n\tlet changed = false;\n\tconst out = nodes.map((n) => {\n\t\tif (n.type !== 'group') return n;\n\t\tconst inner = refreshVectorBlends(n.children);\n\t\tlet next: VectorGroupNode = inner === n.children ? n : { ...n, children: inner };\n\t\tif (next.blend) {\n\t\t\tconst children = blendChildren(next);\n\t\t\tif (children !== next.children) next = { ...next, children };\n\t\t}\n\t\tif (next !== n) changed = true;\n\t\treturn next;\n\t});\n\treturn changed ? out : (nodes as VectorNode[]);\n}\n\n/** A step or key with the generated flags dropped and fresh ids. */\nfunction plain(node: VectorNode, newId: () => string): VectorNode {\n\tconst { blendStep: _s, locked: _l, ...rest } = node;\n\tconst renamed = { ...rest, id: newId() } as VectorNode;\n\tif (renamed.type === 'group') renamed.children = renamed.children.map((c) => plain(c, newId));\n\treturn renamed;\n}\n\n/** Expand: the blend as a plain group of its keys and steps (steps get fresh ids). */\nexport function expandVectorBlend(group: VectorGroupNode, newId: () => string): VectorGroupNode {\n\tconst { blend: _b, ...rest } = group;\n\treturn { ...rest, children: group.children.map((c) => (c.blendStep ? plain(c, newId) : c)) };\n}\n\n/**\n * Release: the blend's keys in the group's parent space (the group's transform and opacity\n * applied), followed by the replaced spine as an unpainted path when there is one.\n */\nexport function releaseVectorBlend(group: VectorGroupNode, newId: () => string): VectorNode[] {\n\tconst m = nodeMatrix(group);\n\tconst opacity = group.opacity;\n\tconst keys = vectorBlendKeys(group)\n\t\t.map((c) => (group.transform ? transformVectorNode(c, m) : c))\n\t\t.map((c) => (opacity !== undefined && opacity < 1 ? { ...c, opacity: (c.opacity ?? 1) * opacity } : c));\n\tconst spine = group.blend?.spine;\n\tif (!spine || spine.anchors.length < 2) return keys;\n\tconst path: VectorPathNode = { id: newId(), type: 'path', name: 'Spine', fill: null, stroke: null, subpaths: [{ closed: spine.closed, anchors: spine.anchors.map((a) => ({ ...a })) }] };\n\treturn [...keys, group.transform ? transformVectorNode(path, m) : path];\n}\n","/**\n * Smart guides and object snapping for the vector editor. Targets — anchors, bounding-box\n * edges and centres, path segments and the artboard — are gathered once per gesture in\n * document space (rotated nodes included) and kept sorted by x and y, so each pointer move\n * finds the few near the pointer by bisection. A point snaps, in this order of priority: kept\n * to its Shift angle and slid into line with a target, onto an anchor or centre, onto an\n * intersection of two paths, onto the perpendicular foot or tangent point from where it\n * leaves, onto a path, onto a construction angle, then into line with targets on x and y. A\n * moved box lines its edges and centre up with targets, or spaces itself evenly in its row\n * and column; a resized box lands its moving edges on targets, or matches another object's\n * width or height. Every snap reports labelled guide lines in document units.\n *\n * Portions ported from vectorcraft-main/crates/tools/src/guides.rs (`Targets`, `within`,\n * `for_move`, `for_drawing`, `for_handle`, `for_anchor_drag`, `push_segments`, `snap_from`,\n * `slide`, `lined_up`, `on_segment`, `crossing`, `align_rects`, `snap_rects`, `even_spacing`,\n * `gap_marks`, `snap_scale`, `Leave`), figcraft-main/crates/engine/src/snap.rs (`spacings`,\n * `equal_spacing`, `spacing_guides`: equal-spacing cases and gap labels),\n * designcraft-main/crates/tools/src/snap.rs (`pass_dimensions`: matching other objects'\n * sizes) and cadcraft-main/crates/engine/src/snap/curves.rs (`Param::solve`: perpendicular and\n * tangent points by sign-change sampling and bisection) (MIT OR Apache-2.0, © ArtCraft Team\n * and contributors). See THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from '../canvas/types.js';\nimport { expandVectorAppearance } from './appearance.js';\nimport { segmentDerivative, segmentNearest, segmentPoint, type BezierSegment } from '../canvas/pathops/bezier.js';\nimport { segmentIntersections } from '../canvas/pathops/boolean.js';\nimport {\n\tapplyMatrix,\n\tIDENTITY_MATRIX,\n\tmatrixRotation,\n\tmultiplyMatrices,\n\tnodeMatrix,\n\trotateMatrix,\n\tsubpathSegment,\n\tsubpathSegmentCount,\n\tvectorNodeBounds,\n\ttype AffineMatrix\n} from './geometry.js';\nimport type { VectorAnchorRef, VectorBounds, VectorDocument, VectorNode, VectorSubpath, VectorTransformHandle, Vec2 } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * What a guide line shows: `align` lines run from a target through the snapped point or box,\n * `construction` lines from where a drawn point leaves, `spacing` marks equal gaps and `size`\n * the side whose length matches another object's.\n */\nexport type VectorGuideKind = 'align' | 'construction' | 'spacing' | 'size';\n\n/** A guide line in document units. */\nexport interface VectorGuideLine {\n\tkind: VectorGuideKind;\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n}\n\n/** What a point or box snapped to. */\nexport type VectorSnapKind = 'anchor' | 'centre' | 'intersect' | 'perpendicular' | 'tangent' | 'path' | 'construction' | 'align' | 'spacing' | 'size';\n\n/** A guide label in document units: the snap kind's name, or a distance for gaps and sizes. */\nexport interface VectorGuideLabel {\n\tkind: VectorSnapKind;\n\tx: number;\n\ty: number;\n\ttext: string;\n}\n\nexport interface VectorSnapGuides {\n\tlines: VectorGuideLine[];\n\tlabels: VectorGuideLabel[];\n}\n\nexport interface VectorPointSnap extends VectorSnapGuides {\n\tpoint: Vec2;\n\t/** The rule that placed the point, or null when nothing was in reach. */\n\tkind: VectorSnapKind | null;\n\t/** Whether x / y were set by a target (for falling back to the grid on the other axis). */\n\tx: boolean;\n\ty: boolean;\n}\n\nexport interface VectorRectSnap extends VectorSnapGuides {\n\t/** Correction to add to the move. */\n\tdx: number;\n\tdy: number;\n\tx: boolean;\n\ty: boolean;\n}\n\nexport interface VectorScaleSnap extends VectorSnapGuides {\n\tsx: number;\n\tsy: number;\n\tsnapped: boolean;\n}\n\n/**\n * The point a drawn point leaves from (the anchor before it, a shape's start, a handle's\n * anchor) and how it keeps to it.\n */\nexport interface VectorSnapLeave {\n\tat: Vec2;\n\t/**\n\t * Shift held: keep to 45° steps round `at` (`angle`), to a diagonal (`square`, a box's\n\t * corner) or to the nearer axis (`axis`, a dragged point).\n\t */\n\tconstrain?: 'angle' | 'square' | 'axis' | null;\n\t/** Construction guide angles through `at`, degrees counter-clockwise on screen. */\n\tangles?: readonly number[];\n\t/** Snap to the perpendicular foot and tangent points from `at` on paths (default true). */\n\tcurves?: boolean;\n}\n\n/** The construction guide angles new editors start with. */\nexport const VECTOR_CONSTRUCTION_ANGLES: readonly number[] = [0, 45, 90, 135];\n\n/** Where `constrain` puts `p` (unchanged without one). */\nexport function constrainToLeave(leave: VectorSnapLeave | null | undefined, p: Vec2): Vec2 {\n\tif (!leave?.constrain) return p;\n\tconst dx = p.x - leave.at.x;\n\tconst dy = p.y - leave.at.y;\n\tswitch (leave.constrain) {\n\t\tcase 'axis':\n\t\t\treturn Math.abs(dx) > Math.abs(dy) ? { x: p.x, y: leave.at.y } : { x: leave.at.x, y: p.y };\n\t\tcase 'square': {\n\t\t\tconst s = Math.max(Math.abs(dx), Math.abs(dy));\n\t\t\treturn { x: leave.at.x + Math.sign(dx || 1) * s, y: leave.at.y + Math.sign(dy || 1) * s };\n\t\t}\n\t\tdefault: {\n\t\t\tconst len = Math.hypot(dx, dy);\n\t\t\tconst a = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);\n\t\t\treturn { x: leave.at.x + Math.cos(a) * len, y: leave.at.y + Math.sin(a) * len };\n\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Targets\n// ---------------------------------------------------------------------------\n\ntype TargetKind = 'anchor' | 'centre' | 'edge' | 'artboard';\n\ninterface PointTarget {\n\tp: Vec2;\n\tkind: 'anchor' | 'centre';\n}\n\n/** A line to line up with: where it runs on its axis and the target point it comes from. */\ninterface LineTarget {\n\tv: number;\n\tfrom: Vec2;\n\tkind: TargetKind;\n}\n\ninterface SegmentTarget {\n\tseg: Point[];\n\tminX: number;\n\tminY: number;\n\tmaxX: number;\n\tmaxY: number;\n}\n\n/** How many anchors, and how many segments, the targets gather at most. */\nconst BUDGET = 20_000;\n/** The first near segments paired up when looking for intersections. */\nconst CROSSING_PAIRS = 32;\n/** Parameter samples per segment for perpendicular / tangent roots, and bisection steps. */\nconst CURVE_SAMPLES = 48;\nconst BISECT = 50;\nconst EQUAL = 1e-6;\n\n/** First index of `sorted` whose key is not below `v` (`strict`: above `v`). */\nfunction bound<T>(sorted: readonly T[], key: (e: T) => number, v: number, strict: boolean): number {\n\tlet lo = 0;\n\tlet hi = sorted.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tconst k = key(sorted[mid]);\n\t\tif (strict ? k <= v : k < v) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\treturn lo;\n}\n\n/** The entries of `sorted` (ascending by `key`) whose key is within `tol` of `v`, by bisection. */\nfunction within<T>(sorted: readonly T[], key: (e: T) => number, v: number, tol: number): readonly T[] {\n\treturn sorted.slice(bound(sorted, key, v - tol, false), bound(sorted, key, v + tol, true));\n}\n\nconst linesNear = (lines: readonly LineTarget[], v: number, tol: number) => within(lines, (l) => l.v, v, tol);\n\nconst distance = (a: Vec2, b: Vec2) => Math.hypot(a.x - b.x, a.y - b.y);\nconst toPoint = (p: Vec2): Point => [p.x, p.y];\nconst toVec = (p: Point): Vec2 => ({ x: p[0], y: p[1] });\n\n/** What `collect` gathers from each path besides its anchors and bounds. */\ntype Gather = 'bounds' | 'segments' | 'drawing';\n\nconst LABELS: Record<VectorSnapKind, string> = {\n\tanchor: 'anchor',\n\tcentre: 'centre',\n\tintersect: 'intersect',\n\tperpendicular: 'perpendicular',\n\ttangent: 'tangent',\n\tpath: 'path',\n\tconstruction: 'angle',\n\talign: 'align',\n\tspacing: 'spacing',\n\tsize: 'size'\n};\n\nconst fmtLength = (n: number) => String(Math.round(n * 100) / 100);\n\n/** A node's own (possibly rotated) frame: its size there and its centre in document space. */\nfunction nodeFrame(node: VectorNode, parent: AffineMatrix, aabb?: VectorBounds | null): { width: number; height: number; centre: Vec2 } | null {\n\tconst angle = matrixRotation(multiplyMatrices(nodeMatrix(node), parent));\n\tconst b = angle ? vectorNodeBounds(node, multiplyMatrices(parent, rotateMatrix(-angle))) : aabb !== undefined ? aabb : vectorNodeBounds(node, parent);\n\tif (!b) return null;\n\tconst c = { x: b.x + b.width / 2, y: b.y + b.height / 2 };\n\treturn { width: b.width, height: b.height, centre: angle ? applyMatrix(rotateMatrix(angle), c) : c };\n}\n\n/** True when `id` is `node` or inside it. */\nfunction contains(node: VectorNode, ids: ReadonlySet<string>): boolean {\n\tif (ids.has(node.id)) return true;\n\treturn node.type === 'group' && node.children.some((c) => contains(c, ids));\n}\n\n/**\n * Snap targets in document space, gathered once per gesture. Build them with `forMove`,\n * `forResize`, `forDrawing`, `forAnchorDrag` or `forHandle`, then call `snapFrom`,\n * `snapRect` or `snapScale` on every pointer move. Tolerances are in document units (screen\n * pixels divided by the zoom).\n */\nexport class VectorSnapTargets {\n\tprivate readonly points: PointTarget[] = [];\n\tprivate readonly xs: LineTarget[] = [];\n\tprivate readonly ys: LineTarget[] = [];\n\tprivate readonly segments: SegmentTarget[] = [];\n\t/** Bounds of the objects a moved box spaces itself evenly with. */\n\tprivate readonly boxes: VectorBounds[] = [];\n\t/** Side lengths of the other objects (in their own frames), which a resize may match. */\n\tprivate readonly sizes: number[] = [];\n\n\tprivate constructor() {}\n\n\t/** How many targets of each sort were gathered. */\n\tget counts(): { points: number; lines: number; segments: number; boxes: number } {\n\t\treturn { points: this.points.length, lines: this.xs.length + this.ys.length, segments: this.segments.length, boxes: this.boxes.length };\n\t}\n\n\t/** Targets for moving nodes `ids`: the other art's anchors, bounds and centres, the artboard, and boxes to space evenly with. */\n\tstatic forMove(doc: VectorDocument, ids: readonly string[]): VectorSnapTargets {\n\t\treturn VectorSnapTargets.collect(doc, new Set(ids), 'bounds');\n\t}\n\n\t/** Targets for resizing nodes `ids`: as for a move, plus the other objects' sizes. */\n\tstatic forResize(doc: VectorDocument, ids: readonly string[]): VectorSnapTargets {\n\t\treturn VectorSnapTargets.collect(doc, new Set(ids), 'bounds');\n\t}\n\n\t/**\n\t * Targets for a drawn point (pen, shape tools): the art but `exclude`, its anchors (as\n\t * points and in line), centres, edges and segments. The anchors and segments of `exclude`\n\t * (the path being drawn, whose bounds grow with it) stay targets.\n\t */\n\tstatic forDrawing(doc: VectorDocument, exclude: readonly string[] = []): VectorSnapTargets {\n\t\tconst skip = new Set(exclude);\n\t\tconst t = VectorSnapTargets.collect(doc, skip, 'drawing', false);\n\t\tt.pushPaths(doc, skip, () => false, true);\n\t\treturn t.index();\n\t}\n\n\t/**\n\t * Targets for dragging anchors: the other art with its segments, and the anchors and\n\t * segments that stay put on the paths being reshaped (their bounds move along, so they are\n\t * no targets). The moving anchors are left out.\n\t */\n\tstatic forAnchorDrag(doc: VectorDocument, anchors: readonly VectorAnchorRef[]): VectorSnapTargets {\n\t\tconst moving = new Map<string, Set<string>>();\n\t\tfor (const r of anchors) {\n\t\t\tconst set = moving.get(r.nodeId) ?? new Set<string>();\n\t\t\tset.add(`${r.subpath}:${r.index}`);\n\t\t\tmoving.set(r.nodeId, set);\n\t\t}\n\t\tconst t = VectorSnapTargets.collect(doc, new Set(moving.keys()), 'segments', false);\n\t\tt.pushPaths(doc, new Set(moving.keys()), (id, s, i) => !!moving.get(id)?.has(`${s}:${i}`), true);\n\t\treturn t.index();\n\t}\n\n\t/**\n\t * Targets for dragging a handle of path `nodeId`: the other art and that path's anchors\n\t * (its bounds and the segments through the handle move with it, so they would chase it).\n\t */\n\tstatic forHandle(doc: VectorDocument, nodeId: string): VectorSnapTargets {\n\t\tconst skip = new Set([nodeId]);\n\t\tconst t = VectorSnapTargets.collect(doc, skip, 'bounds', false);\n\t\tt.pushPaths(doc, skip, () => false, false);\n\t\treturn t.index();\n\t}\n\n\t/** The anchors of the paths `ids` as targets (on them and in line), and their segments when `segments`. */\n\tprivate pushPaths(doc: VectorDocument, ids: ReadonlySet<string>, moving: (id: string, subpath: number, index: number) => boolean, segments: boolean) {\n\t\tlet anchors = BUDGET;\n\t\tlet segmentBudget = segments ? BUDGET : 0;\n\t\tconst visit = (list: readonly VectorNode[], parent: AffineMatrix) => {\n\t\t\tfor (const n of list) {\n\t\t\t\tconst m = multiplyMatrices(nodeMatrix(n), parent);\n\t\t\t\tif (n.type === 'group') visit(n.children, m);\n\t\t\t\telse if (ids.has(n.id) && n.visible !== false) {\n\t\t\t\t\tn.subpaths.forEach((sp, s) => {\n\t\t\t\t\t\tsp.anchors.forEach((a, i) => {\n\t\t\t\t\t\t\tif (anchors <= 0 || moving(n.id, s, i)) return;\n\t\t\t\t\t\t\tthis.pushAnchor(applyMatrix(m, a));\n\t\t\t\t\t\t\tanchors--;\n\t\t\t\t\t\t});\n\t\t\t\t\t\tsegmentBudget = this.pushSegments(sp, m, (i) => moving(n.id, s, i), segmentBudget);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tvisit(doc.nodes, IDENTITY_MATRIX);\n\t}\n\n\tprivate pushAnchor(p: Vec2) {\n\t\tthis.points.push({ p, kind: 'anchor' });\n\t\tthis.xs.push({ v: p.x, from: p, kind: 'anchor' });\n\t\tthis.ys.push({ v: p.y, from: p, kind: 'anchor' });\n\t}\n\n\t/** The segments of `sp` (mapped by `m`) that stay put while the anchors `moving` accepts move. */\n\tprivate pushSegments(sp: VectorSubpath, m: AffineMatrix, moving: (index: number) => boolean, budget: number): number {\n\t\tconst n = sp.anchors.length;\n\t\tconst count = subpathSegmentCount(sp);\n\t\tfor (let i = 0; i < count && budget > 0; i++) {\n\t\t\tif (moving(i) || moving((i + 1) % n)) continue;\n\t\t\tconst seg = subpathSegment(sp, i).map((q) => toPoint(applyMatrix(m, toVec(q))));\n\t\t\tlet minX = Infinity;\n\t\t\tlet minY = Infinity;\n\t\t\tlet maxX = -Infinity;\n\t\t\tlet maxY = -Infinity;\n\t\t\tfor (const [x, y] of seg) {\n\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t}\n\t\t\tthis.segments.push({ seg, minX, minY, maxX, maxY });\n\t\t\tbudget--;\n\t\t}\n\t\treturn budget;\n\t}\n\n\tprivate pushRect(r: VectorBounds, kind: TargetKind, centre: Vec2 | null) {\n\t\tconst corners = [\n\t\t\t{ x: r.x, y: r.y },\n\t\t\t{ x: r.x + r.width, y: r.y },\n\t\t\t{ x: r.x + r.width, y: r.y + r.height },\n\t\t\t{ x: r.x, y: r.y + r.height }\n\t\t];\n\t\tfor (const p of corners) {\n\t\t\tthis.xs.push({ v: p.x, from: p, kind });\n\t\t\tthis.ys.push({ v: p.y, from: p, kind });\n\t\t}\n\t\tif (centre) {\n\t\t\tthis.points.push({ p: centre, kind: 'centre' });\n\t\t\tthis.xs.push({ v: centre.x, from: centre, kind: 'centre' });\n\t\t\tthis.ys.push({ v: centre.y, from: centre, kind: 'centre' });\n\t\t}\n\t}\n\n\t/** Sorts the targets for `within` (stable: of targets alike, the first gathered wins). */\n\tprivate index(): this {\n\t\tthis.points.sort((a, b) => a.p.x - b.p.x);\n\t\tthis.xs.sort((a, b) => a.v - b.v);\n\t\tthis.ys.sort((a, b) => a.v - b.v);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gathers targets from the artboard and every visible node but `exclude` (and what is inside\n\t * those): each leaf's anchors (also as lines for `drawing`), its segments (`segments`,\n\t * `drawing`), its bounds' corners as lines and its centre; and the top-level objects' boxes and\n\t * sizes (inside a group holding an excluded node, that group's other members).\n\t */\n\tprivate static collect(doc: VectorDocument, exclude: ReadonlySet<string>, gather: Gather, sort = true): VectorSnapTargets {\n\t\tconst self = new VectorSnapTargets();\n\t\tconst artboard = { x: 0, y: 0, width: doc.width, height: doc.height };\n\t\tself.pushRect(artboard, 'artboard', { x: doc.width / 2, y: doc.height / 2 });\n\t\tlet anchors = BUDGET;\n\t\tlet segments = BUDGET;\n\t\tconst visit = (list: readonly VectorNode[], parent: AffineMatrix) => {\n\t\t\tfor (const stored of list) {\n\t\t\t\tif (exclude.has(stored.id) || stored.visible === false) continue;\n\t\t\t\t// Other art snaps where it draws (path effects and live booleans applied).\n\t\t\t\tconst n = contains(stored, exclude) ? stored : expandVectorAppearance(stored);\n\t\t\t\tconst m = multiplyMatrices(nodeMatrix(n), parent);\n\t\t\t\tif (n.type === 'group') {\n\t\t\t\t\tvisit(n.children, m);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (anchors <= 0) continue;\n\t\t\t\tfor (const sp of n.subpaths) {\n\t\t\t\t\tfor (const a of sp.anchors) {\n\t\t\t\t\t\tif (anchors-- <= 0) break;\n\t\t\t\t\t\tconst p = applyMatrix(m, a);\n\t\t\t\t\t\tif (gather === 'drawing') self.pushAnchor(p);\n\t\t\t\t\t\telse self.points.push({ p, kind: 'anchor' });\n\t\t\t\t\t}\n\t\t\t\t\tif (gather !== 'bounds') segments = self.pushSegments(sp, m, () => false, segments);\n\t\t\t\t}\n\t\t\t\tconst b = vectorNodeBounds(n, parent);\n\t\t\t\tconst f = nodeFrame(n, parent, b);\n\t\t\t\tif (b) self.pushRect(b, 'edge', f?.centre ?? null);\n\t\t\t}\n\t\t};\n\t\tvisit(doc.nodes, IDENTITY_MATRIX);\n\t\t// Boxes to space evenly with and sizes to match: whole objects, opening groups that hold an excluded node.\n\t\tconst objects = (list: readonly VectorNode[], parent: AffineMatrix) => {\n\t\t\tfor (const n of list) {\n\t\t\t\tif (exclude.has(n.id) || n.visible === false || self.boxes.length >= BUDGET) continue;\n\t\t\t\tif (n.type === 'group' && contains(n, exclude)) {\n\t\t\t\t\tobjects(n.children, multiplyMatrices(nodeMatrix(n), parent));\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst b = vectorNodeBounds(n, parent);\n\t\t\t\tif (b) self.boxes.push(b);\n\t\t\t\tconst f = nodeFrame(n, parent, b);\n\t\t\t\tif (f) self.sizes.push(f.width, f.height);\n\t\t\t}\n\t\t};\n\t\tobjects(doc.nodes, IDENTITY_MATRIX);\n\t\treturn sort ? self.index() : self;\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Points\n\t// -------------------------------------------------------------------------\n\n\t/** Snaps a single point (see `snapFrom`). */\n\tsnapPoint(p: Vec2, tol: number): VectorPointSnap {\n\t\treturn this.snapFrom(p, null, tol);\n\t}\n\n\t/**\n\t * Snaps a point `p`, drawn or dragged from `leave` when given. With `leave.constrain` it keeps\n\t * to its angle round `leave.at`, sliding along that way into line with the nearest target.\n\t * Otherwise it lands, in order, on an anchor or centre, an intersection of two paths, the\n\t * perpendicular foot or a tangent point from `leave.at`, a path, a construction guide\n\t * through `leave.at` (where a target's line crosses it if one does nearby), or else lines up\n\t * with targets on x and y.\n\t */\n\tsnapFrom(p: Vec2, leave: VectorSnapLeave | null | undefined, tol: number): VectorPointSnap {\n\t\tconst none = (q: Vec2): VectorPointSnap => ({ point: q, kind: null, x: false, y: false, lines: [], labels: [] });\n\t\tconst at = (q: Vec2, kind: VectorSnapKind, lines: VectorGuideLine[] = []): VectorPointSnap => ({\n\t\t\tpoint: q,\n\t\t\tkind,\n\t\t\tx: true,\n\t\t\ty: true,\n\t\t\tlines,\n\t\t\tlabels: [this.label(q, kind)]\n\t\t});\n\t\tif (leave?.constrain) {\n\t\t\tconst q = constrainToLeave(leave, p);\n\t\t\tconst hit = this.slide(leave.at, { x: q.x - leave.at.x, y: q.y - leave.at.y }, q, tol, true);\n\t\t\treturn hit ? { point: hit.point, x: true, y: true, ...this.linedUp(hit.point, hit.from) } : none(q);\n\t\t}\n\t\tconst point = this.nearestPoint(p, tol);\n\t\tif (point) return at(point.p, point.kind);\n\t\tconst near = this.segmentsNear(p, tol);\n\t\tconst crossing = this.crossing(near, p, tol);\n\t\tif (crossing) return at(crossing, 'intersect');\n\t\tif (leave && leave.curves !== false) {\n\t\t\tconst foot = this.curvePoint(near, leave.at, p, tol);\n\t\t\tif (foot) return at(foot.point, foot.kind, [line('construction', leave.at, foot.point)]);\n\t\t}\n\t\tconst on = this.onSegment(near);\n\t\tif (on) return at(on, 'path');\n\t\tif (leave?.angles?.length) {\n\t\t\tconst c = construction(leave, p, tol);\n\t\t\tif (c) {\n\t\t\t\tconst hit = this.slide(leave.at, c.dir, c.point, tol, false);\n\t\t\t\tif (hit) {\n\t\t\t\t\tconst lined = this.linedUp(hit.point, hit.from);\n\t\t\t\t\treturn { point: hit.point, x: true, y: true, kind: lined.kind, lines: [line('construction', leave.at, hit.point), ...lined.lines], labels: lined.labels };\n\t\t\t\t}\n\t\t\t\treturn { point: c.point, kind: 'construction', x: true, y: true, lines: [line('construction', leave.at, c.point)], labels: [] };\n\t\t\t}\n\t\t}\n\t\tconst nearest = (lines: readonly LineTarget[], v: number) => {\n\t\t\tlet best: LineTarget | null = null;\n\t\t\tfor (const l of linesNear(lines, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.v - v)) best = l;\n\t\t\treturn best;\n\t\t};\n\t\tconst bx = nearest(this.xs, p.x);\n\t\tconst by = nearest(this.ys, p.y);\n\t\tif (!bx && !by) return none(p);\n\t\tconst out = { x: bx ? bx.v : p.x, y: by ? by.v : p.y };\n\t\tconst lines: VectorGuideLine[] = [];\n\t\tif (bx) lines.push(line('align', bx.from, { x: out.x, y: out.y }));\n\t\tif (by) lines.push(line('align', by.from, { x: out.x, y: out.y }));\n\t\treturn { point: out, kind: 'align', x: !!bx, y: !!by, lines, labels: [this.label(out, 'align')] };\n\t}\n\n\tprivate label(p: Vec2, kind: VectorSnapKind, text = LABELS[kind]): VectorGuideLabel {\n\t\treturn { kind, x: p.x, y: p.y, text };\n\t}\n\n\t/** The anchor or centre nearest `p`, within `tol`. */\n\tprivate nearestPoint(p: Vec2, tol: number): PointTarget | null {\n\t\tlet best: PointTarget | null = null;\n\t\tlet bestD = Infinity;\n\t\tfor (const t of within(this.points, (e) => e.p.x, p.x, tol)) {\n\t\t\tconst d = distance(t.p, p);\n\t\t\tif (d <= tol && d < bestD) {\n\t\t\t\tbest = t;\n\t\t\t\tbestD = d;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\t/**\n\t * Slides `q`, on the line through `at` along `dir` (beyond `at` only for a `ray`), to where\n\t * the nearest target line crosses it, at most twice `tol` away: where it lands and the\n\t * target point that line comes from.\n\t */\n\tprivate slide(at: Vec2, dir: Vec2, q: Vec2, tol: number, ray: boolean): { point: Vec2; from: Vec2 } | null {\n\t\tconst len2 = dir.x * dir.x + dir.y * dir.y;\n\t\tif (!Number.isFinite(len2) || len2 <= 1e-18) return null;\n\t\tconst reach = 2 * tol;\n\t\tlet best: { moved: number; point: Vec2; from: Vec2 } | null = null;\n\t\tfor (const vertical of [true, false]) {\n\t\t\tconst d = vertical ? dir.x : dir.y;\n\t\t\tif (Math.abs(d) <= 1e-9) continue;\n\t\t\tconst a = vertical ? at.x : at.y;\n\t\t\tfor (const l of linesNear(vertical ? this.xs : this.ys, vertical ? q.x : q.y, reach)) {\n\t\t\t\tconst s = (l.v - a) / d;\n\t\t\t\t// Exactly on the line, whatever the rounding along the way.\n\t\t\t\tconst hit = vertical ? { x: l.v, y: at.y + dir.y * s } : { x: at.x + dir.x * s, y: l.v };\n\t\t\t\tconst moved = distance(hit, q);\n\t\t\t\tif (moved <= reach && (!ray || s > 0) && (!best || moved < best.moved)) best = { moved, point: hit, from: l.from };\n\t\t\t}\n\t\t}\n\t\treturn best && { point: best.point, from: best.from };\n\t}\n\n\t/** The guides of a point `q` slid into line with the target at `from`: the line and \"align\", or the target's own label when `q` sits on it. */\n\tprivate linedUp(q: Vec2, from: Vec2): { kind: VectorSnapKind } & VectorSnapGuides {\n\t\tconst on = this.nearestPoint(q, 1e-9);\n\t\tif (on) return { kind: on.kind, lines: [], labels: [this.label(on.p, on.kind)] };\n\t\treturn { kind: 'align', lines: [line('align', from, q)], labels: [this.label(q, 'align')] };\n\t}\n\n\t/** Target segments whose control box reaches `p` and which pass within `tol` of it, nearest first. */\n\tprivate segmentsNear(p: Vec2, tol: number): { seg: Point[]; t: number; distance: number }[] {\n\t\tconst out: { seg: Point[]; t: number; distance: number }[] = [];\n\t\tconst pt = toPoint(p);\n\t\tfor (const s of this.segments) {\n\t\t\tif (p.x < s.minX - tol || p.x > s.maxX + tol || p.y < s.minY - tol || p.y > s.maxY + tol) continue;\n\t\t\tconst hit = segmentNearest(s.seg, pt);\n\t\t\tif (hit.distance <= tol) out.push({ seg: s.seg, t: hit.t, distance: hit.distance });\n\t\t}\n\t\treturn out.sort((a, b) => a.distance - b.distance);\n\t}\n\n\tprivate onSegment(near: readonly { seg: Point[]; t: number }[]): Vec2 | null {\n\t\treturn near.length ? toVec(segmentPoint(near[0].seg, near[0].t)) : null;\n\t}\n\n\t/** Where two of the target segments near `p` cross, the crossing nearest `p` within `tol`. */\n\tprivate crossing(near: readonly { seg: Point[] }[], p: Vec2, tol: number): Vec2 | null {\n\t\tconst list = near.slice(0, CROSSING_PAIRS);\n\t\tlet best: Vec2 | null = null;\n\t\tlet bestD = Infinity;\n\t\tfor (let i = 0; i < list.length; i++) {\n\t\t\tfor (let j = i + 1; j < list.length; j++) {\n\t\t\t\tfor (const hit of segmentIntersections(list[i].seg as BezierSegment, list[j].seg as BezierSegment)) {\n\t\t\t\t\tconst q = toVec(hit.point);\n\t\t\t\t\tconst d = distance(q, p);\n\t\t\t\t\tif (d <= tol && d < bestD) {\n\t\t\t\t\t\tbest = q;\n\t\t\t\t\t\tbestD = d;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\t/**\n\t * The foot of a perpendicular, or a tangent point, from `base` on a target segment near `p`\n\t * (within `tol` of it): roots of `(B(t) − base) · B′(t)` and `(B(t) − base) × B′(t)`\n\t * bracketed by sign changes of samples and narrowed by bisection.\n\t */\n\tprivate curvePoint(near: readonly { seg: Point[] }[], base: Vec2, p: Vec2, tol: number): { point: Vec2; kind: 'perpendicular' | 'tangent' } | null {\n\t\tlet best: { point: Vec2; kind: 'perpendicular' | 'tangent'; d: number } | null = null;\n\t\tfor (const { seg } of near) {\n\t\t\tfor (const kind of ['perpendicular', 'tangent'] as const) {\n\t\t\t\tif (kind === 'tangent' && seg.length === 2) continue;\n\t\t\t\tfor (const q of solveOnSegment(seg, base, kind === 'perpendicular' ? dotCondition : crossCondition)) {\n\t\t\t\t\tconst d = distance(q, p);\n\t\t\t\t\tif (d <= tol && (!best || d < best.d)) best = { point: q, kind, d };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn best && { point: best.point, kind: best.kind };\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Boxes\n\t// -------------------------------------------------------------------------\n\n\t/**\n\t * Snaps a moving box `r` (the moved selection's bounds): its edge or centre nearest a target\n\t * line on each axis (`axes` limits which may move). With `spacing`, an axis that lines up\n\t * with nothing spaces the box evenly in its row or column. Returns the correction and guides.\n\t */\n\tsnapRect(r: VectorBounds, tol: number, options: { spacing?: boolean; axes?: { x?: boolean; y?: boolean } } = {}): VectorRectSnap {\n\t\tconst axes = { x: options.axes?.x !== false, y: options.axes?.y !== false };\n\t\tconst nearest = (lines: readonly LineTarget[], values: number[]) => {\n\t\t\tlet best: { d: number; from: Vec2 } | null = null;\n\t\t\tfor (const v of values) for (const l of linesNear(lines, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.d)) best = { d: l.v - v, from: l.from };\n\t\t\treturn best;\n\t\t};\n\t\tconst bx = axes.x ? nearest(this.xs, [r.x, r.x + r.width / 2, r.x + r.width]) : null;\n\t\tconst by = axes.y ? nearest(this.ys, [r.y, r.y + r.height / 2, r.y + r.height]) : null;\n\t\tconst out: VectorRectSnap = { dx: bx?.d ?? 0, dy: by?.d ?? 0, x: !!bx, y: !!by, lines: [], labels: [] };\n\t\tconst done = { ...r, x: r.x + out.dx, y: r.y + out.dy };\n\t\tif (bx) {\n\t\t\tconst x = bx.from.x;\n\t\t\tout.lines.push({ kind: 'align', x1: x, y1: Math.min(bx.from.y, done.y), x2: x, y2: Math.max(bx.from.y, done.y + done.height) });\n\t\t}\n\t\tif (by) {\n\t\t\tconst y = by.from.y;\n\t\t\tout.lines.push({ kind: 'align', x1: Math.min(by.from.x, done.x), y1: y, x2: Math.max(by.from.x, done.x + done.width), y2: y });\n\t\t}\n\t\tif (options.spacing && this.boxes.length) {\n\t\t\tfor (const horizontal of [true, false]) {\n\t\t\t\tif (horizontal ? out.x || !axes.x : out.y || !axes.y) continue;\n\t\t\t\tconst moved = { ...r, x: r.x + out.dx, y: r.y + out.dy };\n\t\t\t\tconst even = this.evenSpacing(moved, horizontal, tol);\n\t\t\t\tif (!even) continue;\n\t\t\t\tif (horizontal) {\n\t\t\t\t\tout.dx += even.shift;\n\t\t\t\t\tout.x = true;\n\t\t\t\t} else {\n\t\t\t\t\tout.dy += even.shift;\n\t\t\t\t\tout.y = true;\n\t\t\t\t}\n\t\t\t\tfor (const gap of even.gaps) this.gapMarks(gap, horizontal, tol, out);\n\t\t\t}\n\t\t}\n\t\tif (bx || by) out.labels.unshift(this.label({ x: r.x + out.dx + r.width / 2, y: r.y + out.dy + r.height / 2 }, 'align'));\n\t\treturn out;\n\t}\n\n\t/**\n\t * Equal spacing along x (`horizontal`) or y for the moved box `r`, among the objects of its\n\t * row (column): the shift within `tol` that centres it between its neighbours, or leaves it\n\t * as far from the neighbour before or after it as two other objects of the row are apart;\n\t * and the gaps that are equal then, as (start, end, where across).\n\t */\n\tprivate evenSpacing(r: VectorBounds, horizontal: boolean, tol: number): { shift: number; gaps: [number, number, number][] } | null {\n\t\tconst span = (b: VectorBounds, x: boolean): [number, number] => (x ? [b.x, b.x + b.width] : [b.y, b.y + b.height]);\n\t\tconst [r0, r1] = span(r, horizontal);\n\t\tconst len = r1 - r0;\n\t\tconst [c0, c1] = span(r, !horizontal);\n\t\tconst row = this.boxes\n\t\t\t.map((b) => ({ b, a0: span(b, horizontal)[0], a1: span(b, horizontal)[1] }))\n\t\t\t.filter((e) => span(e.b, !horizontal)[0] < c1 && span(e.b, !horizontal)[1] > c0 && (e.a1 <= r0 + tol || e.a0 >= r1 - tol))\n\t\t\t.sort((a, b) => a.a0 - b.a0);\n\t\tif (!row.length) return null;\n\t\t// Across the middle of what two boxes share (or of the space between them).\n\t\tconst mid = (a: VectorBounds, b: VectorBounds) => (Math.max(span(a, !horizontal)[0], span(b, !horizontal)[0]) + Math.min(span(a, !horizontal)[1], span(b, !horizontal)[1])) / 2;\n\t\tconst pairs: { g: number; gap: [number, number, number] }[] = [];\n\t\tfor (const p of row) {\n\t\t\tconst next = bound(row, (e) => e.a0, p.a1, false);\n\t\t\tconst q = row[next];\n\t\t\tif (!q) continue;\n\t\t\tconst straddles = p.a1 <= r0 + tol && q.a0 >= r1 - tol;\n\t\t\tif (q.a0 > p.a1 && !straddles) pairs.push({ g: q.a0 - p.a1, gap: [p.a1, q.a0, mid(p.b, q.b)] });\n\t\t}\n\t\tlet before: (typeof row)[number] | null = null;\n\t\tlet after: (typeof row)[number] | null = null;\n\t\tfor (const e of row) {\n\t\t\tif (e.a1 <= r0 + tol && (!before || e.a1 > before.a1)) before = e;\n\t\t\tif (e.a0 >= r1 - tol && (!after || e.a0 < after.a0)) after = e;\n\t\t}\n\t\tconst starts: { start: number; repeats: number | null }[] = [];\n\t\tif (before && after && after.a0 - before.a1 > len) starts.push({ start: (before.a1 + after.a0 - len) / 2, repeats: null });\n\t\tfor (const { g } of pairs) {\n\t\t\tif (before) starts.push({ start: before.a1 + g, repeats: g });\n\t\t\tif (after) starts.push({ start: after.a0 - g - len, repeats: g });\n\t\t}\n\t\tlet pick: { start: number; repeats: number | null } | null = null;\n\t\tfor (const s of starts) if (Math.abs(s.start - r0) <= tol && (!pick || Math.abs(s.start - r0) < Math.abs(pick.start - r0))) pick = s;\n\t\tif (!pick) return null;\n\t\tconst { start, repeats } = pick;\n\t\tconst at = horizontal ? { ...r, x: start } : { ...r, y: start };\n\t\tconst end = start + len;\n\t\tconst gaps: [number, number, number][] = [];\n\t\tif (before && (repeats === null || Math.abs(start - before.a1 - repeats) <= EQUAL)) gaps.push([before.a1, start, mid(before.b, at)]);\n\t\tif (after && (repeats === null || Math.abs(after.a0 - end - repeats) <= EQUAL)) gaps.push([end, after.a0, mid(after.b, at)]);\n\t\tif (repeats !== null) for (const p of pairs) if (Math.abs(p.g - repeats) <= EQUAL) gaps.push(p.gap);\n\t\treturn { shift: start - r0, gaps };\n\t}\n\n\t/** A gap mark: a line across the gap with a tick at each end, and its length as a label. */\n\tprivate gapMarks([u0, u1, c]: [number, number, number], horizontal: boolean, tick: number, out: VectorSnapGuides) {\n\t\tconst pt = (u: number, v: number): Vec2 => (horizontal ? { x: u, y: v } : { x: v, y: u });\n\t\tout.lines.push(line('spacing', pt(u0, c), pt(u1, c)), line('spacing', pt(u0, c - tick), pt(u0, c + tick)), line('spacing', pt(u1, c - tick), pt(u1, c + tick)));\n\t\tout.labels.push(this.label(pt((u0 + u1) / 2, c), 'spacing', fmtLength(u1 - u0)));\n\t}\n\n\t/** The other objects' side length nearest `length`, within `tol`, or null. */\n\tmatchSize(length: number, tol: number): number | null {\n\t\tlet best: number | null = null;\n\t\tfor (const s of this.sizes) if (Math.abs(s - length) <= tol && (best === null || Math.abs(s - length) < Math.abs(best - length))) best = s;\n\t\treturn best;\n\t}\n\n\t/**\n\t * Snaps a selection-box resize. `sx / sy` are the scale `scaleFor` gave for dragging\n\t * `handle` of `frame` (in the box's own, possibly rotated frame), `mods.shift` keeps the\n\t * proportions and `mods.alt` scales about the centre. The handle lands on an anchor or\n\t * centre within `tol`, else the corner or the side it moves lines up with the nearest\n\t * target, whatever the box's angle; a handle with one way to go (a side, or a proportional\n\t * corner) slides along it until one of them is on a target. An axis that snaps to nothing\n\t * may match another object's width or height instead.\n\t */\n\tsnapScale(\n\t\tframe: VectorBounds & { angle: number },\n\t\thandle: Exclude<VectorTransformHandle, 'rotate'>,\n\t\tsx: number,\n\t\tsy: number,\n\t\tmods: { shift?: boolean; alt?: boolean },\n\t\ttol: number\n\t): VectorScaleSnap {\n\t\tconst toDoc = frame.angle ? rotateMatrix(frame.angle) : IDENTITY_MATRIX;\n\t\tconst toLocal = frame.angle ? rotateMatrix(-frame.angle) : IDENTITY_MATRIX;\n\t\tconst corner = handle.length === 2;\n\t\tconst proportional = !!mods.shift;\n\t\tconst centre = { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 };\n\t\tconst origin = mods.alt ? centre : handlePos(opposite(handle), frame);\n\t\tconst hp = handlePos(handle, frame);\n\t\tconst d0 = { x: hp.x - origin.x, y: hp.y - origin.y };\n\t\tconst valid = (n: number, s: number) => Number.isFinite(n) && n * s > 0;\n\t\tconst nearest = (v: number, lines: readonly LineTarget[]) => {\n\t\t\tlet best: LineTarget | null = null;\n\t\t\tfor (const l of linesNear(lines, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.v - v)) best = l;\n\t\t\treturn best;\n\t\t};\n\t\ttype Snap = { hx: LineTarget | null; hy: LineTarget | null; on: PointTarget | null };\n\t\tlet nx = sx;\n\t\tlet ny = sy;\n\t\tlet snap: Snap = { hx: null, hy: null, on: null };\n\t\tif (corner && !proportional) {\n\t\t\t// The corner goes anywhere: onto an anchor or centre, else it takes a target's x, its y, or both.\n\t\t\tconst p = applyMatrix(toDoc, { x: origin.x + d0.x * sx, y: origin.y + d0.y * sy });\n\t\t\tconst hx = nearest(p.x, this.xs);\n\t\t\tconst hy = nearest(p.y, this.ys);\n\t\t\tconst on = this.nearestPoint(p, tol);\n\t\t\tconst tries: { tx: number | null; ty: number | null; s: Snap }[] = [];\n\t\t\tif (on) tries.push({ tx: on.p.x, ty: on.p.y, s: { hx: null, hy: null, on } });\n\t\t\ttries.push({ tx: hx?.v ?? null, ty: hy?.v ?? null, s: { hx, hy, on: null } });\n\t\t\ttries.push({ tx: hx?.v ?? null, ty: null, s: { hx, hy: null, on: null } });\n\t\t\ttries.push({ tx: null, ty: hy?.v ?? null, s: { hx: null, hy, on: null } });\n\t\t\tfor (const { tx, ty, s } of tries) {\n\t\t\t\tif (tx === null && ty === null) continue;\n\t\t\t\tconst q = applyMatrix(toLocal, { x: tx ?? p.x, y: ty ?? p.y });\n\t\t\t\tlet cx = (q.x - origin.x) / d0.x;\n\t\t\t\tlet cy = (q.y - origin.y) / d0.y;\n\t\t\t\t// Square to the page, the axis without a target stays exactly where it was.\n\t\t\t\tif (!frame.angle) {\n\t\t\t\t\tif (tx === null) cx = sx;\n\t\t\t\t\tif (ty === null) cy = sy;\n\t\t\t\t}\n\t\t\t\tif (valid(cx, sx) && valid(cy, sy)) {\n\t\t\t\t\tnx = cx;\n\t\t\t\t\tny = cy;\n\t\t\t\t\tsnap = s;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// One parameter `s`: the scale is `c + s·e` on each axis.\n\t\t\tconst movesX = handle === 'e' || handle === 'w';\n\t\t\tlet e: Vec2;\n\t\t\tlet c: Vec2;\n\t\t\tlet s0: number;\n\t\t\tif (corner) [e, c, s0] = [{ x: Math.sign(sx), y: Math.sign(sy) }, { x: 0, y: 0 }, Math.abs(sx)];\n\t\t\telse if (movesX) [e, c, s0] = proportional ? [{ x: 1, y: Math.sign(sx) }, { x: 0, y: 0 }, sx] : [{ x: 1, y: 0 }, { x: 0, y: 1 }, sx];\n\t\t\telse [e, c, s0] = proportional ? [{ x: Math.sign(sy), y: 1 }, { x: 0, y: 0 }, sy] : [{ x: 0, y: 1 }, { x: 1, y: 0 }, sy];\n\t\t\t// The points that may land on a target: the handle, and the two ends of its side.\n\t\t\tconst i = RING.indexOf(handle);\n\t\t\tconst ends = corner ? [] : [RING[(i + 1) % 8], RING[(i + 7) % 8]];\n\t\t\t// How far the handle travels per unit of `s`: a snap may not drag it far from the pointer.\n\t\t\tconst travel = Math.hypot(e.x * d0.x, e.y * d0.y);\n\t\t\tlet best: { s: number; inLine: boolean; moved: number; snap: Snap } | null = null;\n\t\t\tconst consider = (s: number, inLine: boolean, found: Snap) => {\n\t\t\t\tconst moved = Math.abs(s - s0) * travel;\n\t\t\t\tif (!valid(s, s0) || moved > 2 * tol) return;\n\t\t\t\tif (!best || (inLine === best.inLine ? moved < best.moved : !inLine)) best = { s, inLine, moved, snap: found };\n\t\t\t};\n\t\t\tfor (const h of [handle, ...ends]) {\n\t\t\t\tconst hv = handlePos(h, frame);\n\t\t\t\tconst v = { x: hv.x - origin.x, y: hv.y - origin.y };\n\t\t\t\tconst base = applyMatrix(toDoc, { x: origin.x + c.x * v.x, y: origin.y + c.y * v.y });\n\t\t\t\tconst dir = applyMatrix(toDoc, { x: e.x * v.x, y: e.y * v.y });\n\t\t\t\tconst p = { x: base.x + dir.x * s0, y: base.y + dir.y * s0 };\n\t\t\t\tconst l2 = dir.x * dir.x + dir.y * dir.y;\n\t\t\t\tconst on = l2 > 1e-18 ? this.nearestPoint(p, tol) : null;\n\t\t\t\t// An anchor or centre beside its way: it stops at the nearest point of the way.\n\t\t\t\tif (on) consider(((on.p.x - base.x) * dir.x + (on.p.y - base.y) * dir.y) / l2, false, { hx: null, hy: null, on });\n\t\t\t\tif (Math.abs(dir.x) > 1e-9) {\n\t\t\t\t\tconst hit = nearest(p.x, this.xs);\n\t\t\t\t\tif (hit) consider((hit.v - base.x) / dir.x, true, { hx: hit, hy: null, on: null });\n\t\t\t\t}\n\t\t\t\tif (Math.abs(dir.y) > 1e-9) {\n\t\t\t\t\tconst hit = nearest(p.y, this.ys);\n\t\t\t\t\tif (hit) consider((hit.v - base.y) / dir.y, true, { hx: null, hy: hit, on: null });\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst found = best as { s: number; snap: Snap } | null;\n\t\t\tif (found) {\n\t\t\t\tnx = c.x + found.s * e.x;\n\t\t\t\tny = c.y + found.s * e.y;\n\t\t\t\tsnap = found.snap;\n\t\t\t}\n\t\t}\n\t\tconst out: VectorScaleSnap = { sx: nx, sy: ny, snapped: false, lines: [], labels: [] };\n\t\tconst handleAt = () => applyMatrix(toDoc, { x: origin.x + d0.x * out.sx, y: origin.y + d0.y * out.sy });\n\t\tif (snap.on) {\n\t\t\tout.snapped = true;\n\t\t\tout.labels.push(this.label(snap.on.p, snap.on.kind));\n\t\t\treturn out;\n\t\t}\n\t\t// Sides that line up with nothing may match another object's width or height.\n\t\tconst movesX = handle !== 'n' && handle !== 's';\n\t\tconst movesY = handle !== 'e' && handle !== 'w';\n\t\tconst sized: ('x' | 'y')[] = [];\n\t\tif (!proportional) {\n\t\t\tif (movesX && !snap.hx && !(frame.angle && snap.hy) && frame.width > 1e-9) {\n\t\t\t\tconst len = this.matchSize(Math.abs(out.sx) * frame.width, tol);\n\t\t\t\tif (len !== null && len > 0) {\n\t\t\t\t\tout.sx = Math.sign(out.sx || 1) * (len / frame.width);\n\t\t\t\t\tsized.push('x');\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (movesY && !snap.hy && !(frame.angle && snap.hx) && frame.height > 1e-9) {\n\t\t\t\tconst len = this.matchSize(Math.abs(out.sy) * frame.height, tol);\n\t\t\t\tif (len !== null && len > 0) {\n\t\t\t\t\tout.sy = Math.sign(out.sy || 1) * (len / frame.height);\n\t\t\t\t\tsized.push('y');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// The resized box in its frame, and its corners on the page.\n\t\tconst x0 = origin.x + (frame.x - origin.x) * out.sx;\n\t\tconst x1 = origin.x + (frame.x + frame.width - origin.x) * out.sx;\n\t\tconst y0 = origin.y + (frame.y - origin.y) * out.sy;\n\t\tconst y1 = origin.y + (frame.y + frame.height - origin.y) * out.sy;\n\t\tconst box = { x0: Math.min(x0, x1), x1: Math.max(x0, x1), y0: Math.min(y0, y1), y1: Math.max(y0, y1) };\n\t\tconst page = [\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((q) => applyMatrix(toDoc, q));\n\t\tconst pminY = Math.min(...page.map((q) => q.y));\n\t\tconst pmaxY = Math.max(...page.map((q) => q.y));\n\t\tconst pminX = Math.min(...page.map((q) => q.x));\n\t\tconst pmaxX = Math.max(...page.map((q) => q.x));\n\t\tif (snap.hx) out.lines.push({ kind: 'align', x1: snap.hx.v, y1: Math.min(snap.hx.from.y, pminY), x2: snap.hx.v, y2: Math.max(snap.hx.from.y, pmaxY) });\n\t\tif (snap.hy) out.lines.push({ kind: 'align', x1: Math.min(snap.hy.from.x, pminX), y1: snap.hy.v, x2: Math.max(snap.hy.from.x, pmaxX), y2: snap.hy.v });\n\t\tif (snap.hx || snap.hy) {\n\t\t\tout.snapped = true;\n\t\t\tout.labels.push(this.label(handleAt(), 'align'));\n\t\t}\n\t\tfor (const axis of sized) {\n\t\t\tout.snapped = true;\n\t\t\t// Along the matched side, just outside the box.\n\t\t\tconst off = tol * 2;\n\t\t\tconst [a, b] = axis === 'x' ? [{ x: box.x0, y: box.y1 + off }, { x: box.x1, y: box.y1 + off }] : [{ x: box.x1 + off, y: box.y0 }, { x: box.x1 + off, y: box.y1 }];\n\t\t\tconst pa = applyMatrix(toDoc, a);\n\t\t\tconst pb = applyMatrix(toDoc, b);\n\t\t\tout.lines.push(line('size', pa, pb));\n\t\t\tout.labels.push(this.label({ x: (pa.x + pb.x) / 2, y: (pa.y + pb.y) / 2 }, 'size', `${axis === 'x' ? 'W' : 'H'} ${fmtLength(axis === 'x' ? box.x1 - box.x0 : box.y1 - box.y0)}`));\n\t\t}\n\t\treturn out;\n\t}\n}\n\n/** The eight resize handles clockwise from the top left (each side sits between its two corners). */\nconst RING: readonly Exclude<VectorTransformHandle, 'rotate'>[] = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];\n\nfunction opposite(h: Exclude<VectorTransformHandle, 'rotate'>): Exclude<VectorTransformHandle, 'rotate'> {\n\treturn RING[(RING.indexOf(h) + 4) % 8];\n}\n\nfunction handlePos(h: Exclude<VectorTransformHandle, 'rotate'>, r: VectorBounds): Vec2 {\n\tconst x = h.includes('w') ? r.x : h.includes('e') ? r.x + r.width : r.x + r.width / 2;\n\tconst y = h.startsWith('n') ? r.y : h.startsWith('s') ? r.y + r.height : r.y + r.height / 2;\n\treturn { x, y };\n}\n\nfunction line(kind: VectorGuideKind, a: Vec2, b: Vec2): VectorGuideLine {\n\treturn { kind, x1: a.x, y1: a.y, x2: b.x, y2: b.y };\n}\n\n/** The construction guide through `leave.at` nearest `p`, within `tol` (and `p` farther than that from `at`): its direction and where `p` lands. */\nfunction construction(leave: VectorSnapLeave, p: Vec2, tol: number): { dir: Vec2; point: Vec2 } | null {\n\tconst v = { x: p.x - leave.at.x, y: p.y - leave.at.y };\n\tif (Math.hypot(v.x, v.y) <= tol) return null;\n\tlet best: { dir: Vec2; point: Vec2; d: number } | null = null;\n\tfor (const a of leave.angles ?? []) {\n\t\t// Counter-clockwise as seen on the y-down page.\n\t\tconst r = (a * Math.PI) / 180;\n\t\t// Exactly along an axis for multiples of 90°.\n\t\tconst exact = (n: number) => (Math.abs(n) < 1e-12 ? 0 : n);\n\t\tconst dir = { x: exact(Math.cos(r)), y: exact(-Math.sin(r)) };\n\t\tconst k = v.x * dir.x + v.y * dir.y;\n\t\tconst point = { x: leave.at.x + dir.x * k, y: leave.at.y + dir.y * k };\n\t\tconst d = distance(point, p);\n\t\tif (d <= tol && (!best || d < best.d)) best = { dir, point, d };\n\t}\n\treturn best && { dir: best.dir, point: best.point };\n}\n\nconst dotCondition = (r: Point, d: Point) => r[0] * d[0] + r[1] * d[1];\nconst crossCondition = (r: Point, d: Point) => r[0] * d[1] - r[1] * d[0];\n\n/**\n * The points of a segment where `cond(B(t) − base, B′(t))` is zero: sign changes of samples,\n * narrowed by bisection, kept when the normalised condition is (nearly) zero there.\n */\nfunction solveOnSegment(seg: readonly Point[], base: Vec2, cond: (r: Point, d: Point) => number): Vec2[] {\n\tconst b: Point = [base.x, base.y];\n\tconst g = (t: number) => {\n\t\tconst p = segmentPoint(seg, t);\n\t\treturn cond([p[0] - b[0], p[1] - b[1]], segmentDerivative(seg, t));\n\t};\n\tconst vals: number[] = [];\n\tlet scale = 0;\n\tfor (let i = 0; i <= CURVE_SAMPLES; i++) {\n\t\tconst t = i / CURVE_SAMPLES;\n\t\tconst p = segmentPoint(seg, t);\n\t\tconst d = segmentDerivative(seg, t);\n\t\tconst r: Point = [p[0] - b[0], p[1] - b[1]];\n\t\tvals.push(cond(r, d));\n\t\tscale = Math.max(scale, Math.hypot(r[0], r[1]) * Math.hypot(d[0], d[1]));\n\t}\n\t// Every point qualifies (a line seen from a point on it): no particular solution.\n\tif (!(scale > 0) || vals.every((v) => Math.abs(v) <= scale * 1e-12)) return [];\n\tconst out: Vec2[] = [];\n\tfor (let i = 0; i < CURVE_SAMPLES; i++) {\n\t\tconst ga = vals[i];\n\t\tconst gb = vals[i + 1];\n\t\tif (!(Number.isFinite(ga) && Number.isFinite(gb)) || (ga > 0 === gb > 0 && ga !== 0)) continue;\n\t\tlet lo = i / CURVE_SAMPLES;\n\t\tlet hi = (i + 1) / CURVE_SAMPLES;\n\t\tlet flo = ga;\n\t\tfor (let k = 0; k < BISECT; k++) {\n\t\t\tconst m = (lo + hi) / 2;\n\t\t\tconst fm = g(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 > 0 === flo > 0) {\n\t\t\t\tlo = m;\n\t\t\t\tflo = fm;\n\t\t\t} else hi = m;\n\t\t}\n\t\tconst t = (lo + hi) / 2;\n\t\tconst p = segmentPoint(seg, t);\n\t\tconst dv = segmentDerivative(seg, t);\n\t\tconst r = Math.hypot(p[0] - b[0], p[1] - b[1]);\n\t\tconst dl = Math.hypot(dv[0], dv[1]);\n\t\tif (!(r > 1e-9 && dl > 0) || Math.abs(cond([p[0] - b[0], p[1] - b[1]], dv)) > 1e-6 * r * dl) continue;\n\t\tif (!out.some((q) => Math.hypot(q.x - p[0], q.y - p[1]) <= 1e-9 * (1 + r))) out.push({ x: p[0], y: p[1] });\n\t}\n\treturn out;\n}\n","/**\n * Path editing on subpaths in a node's own space: anchor deletion that keeps the shape,\n * segment bending, Curvature-tool anchor moves, cutting at anchors (Scissors), Join, Average,\n * Remove Redundant Points and Smooth.\n *\n * Portions ported from (MIT OR Apache-2.0, © ArtCraft Team and contributors; see\n * THIRD_PARTY_NOTICES.md):\n * - vectorcraft-main/crates/pathops/src/edit.rs (`remove_anchor`, `golden_min`, `smooth`,\n * `remove_redundant_points`, `merge_pair`, `average`, `join`)\n * - vectorcraft-main/crates/geom/src/path.rs (`SubPath::cut_at`)\n * - vectorcraft-main/crates/tools/src/draw2/curvature.rs (`curvature_move`)\n * - figcraft-main/crates/engine/src/vectoredit/ops.rs (`bend`)\n */\nimport type { Point } from '../canvas/types.js';\nimport { fitCubicWithTangents } from '../canvas/curveFit.js';\nimport { segmentPoint } from '../canvas/pathops/bezier.js';\nimport { cloneAnchor, inferAnchorType, insertAnchor, moveAnchorHandle, subpathSegment, subpathSegmentCount } from './geometry.js';\nimport type { VectorAnchor, VectorSubpath, Vec2 } from './types.js';\n\nconst EPS = 1e-9;\n\n// ---------------------------------------------------------------------------\n// Small helpers\n// ---------------------------------------------------------------------------\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/** Segment `i` as four control points (a line has its controls at the thirds of its chord). */\nfunction cubicOf(sp: VectorSubpath, i: number): Point[] {\n\tconst s = subpathSegment(sp, i);\n\tif (s.length === 4) return s;\n\tconst [a, b] = s;\n\treturn [a, [a[0] + (b[0] - a[0]) / 3, a[1] + (b[1] - a[1]) / 3], [a[0] + (2 * (b[0] - a[0])) / 3, a[1] + (2 * (b[1] - a[1])) / 3], b];\n}\n\nconst isLine = (sp: VectorSubpath, i: number) => subpathSegment(sp, i).length === 2;\n\n/** Direction the curve leaves `c[0]`. */\nfunction startTangent(c: readonly Point[]): Point {\n\tfor (let k = 1; k < 4; k++) {\n\t\tconst u = unit(c[k][0] - c[0][0], c[k][1] - c[0][1]);\n\t\tif (u) return u;\n\t}\n\treturn [1, 0];\n}\n\n/** Direction the curve arrives at `c[3]` (forward along the curve). */\nfunction endTangent(c: readonly Point[]): Point {\n\tfor (let k = 2; k >= 0; k--) {\n\t\tconst u = unit(c[3][0] - c[k][0], c[3][1] - c[k][1]);\n\t\tif (u) return u;\n\t}\n\treturn [1, 0];\n}\n\n/** `n + 1` points of a cubic at even parameters (`withStart` false skips the first). */\nfunction sample(c: readonly Point[], n: number, out: Point[], withStart: boolean) {\n\tfor (let i = withStart ? 0 : 1; i <= n; i++) out.push(segmentPoint(c, i / n));\n}\n\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/** Drops handles that sit on their anchor and sets the type the handles show (a symmetric pair that is no longer equal becomes smooth). */\nfunction tidy(a: VectorAnchor, keepType = true): VectorAnchor {\n\tconst next = cloneAnchor(a);\n\tif (onAnchor(next, next.in)) delete next.in;\n\tif (onAnchor(next, next.out)) delete next.out;\n\tconst shown = inferAnchorType(next);\n\tconst type = keepType && a.type ? (a.type === 'symmetric' && shown !== 'symmetric' ? (shown === 'corner' ? 'corner' : 'smooth') : a.type === 'smooth' && shown === 'corner' ? 'corner' : a.type) : shown;\n\tif (type === 'corner') delete next.type;\n\telse next.type = type;\n\treturn next;\n}\n\n/** Index of the anchor before / after `i` (round the start of a closed subpath), or -1. */\nfunction neighbours(sp: VectorSubpath, i: number): [number, number] {\n\tconst n = sp.anchors.length;\n\tconst wrap = sp.closed && n > 1;\n\treturn [i > 0 ? i - 1 : wrap ? n - 1 : -1, i < n - 1 ? i + 1 : wrap ? 0 : -1];\n}\n\n// ---------------------------------------------------------------------------\n// Deleting anchors without changing the shape\n// ---------------------------------------------------------------------------\n\n/**\n * The value of `f` with the least error for an argument in [a, b], by a golden-section search\n * (a fixed number of steps). An error that is not a number never wins.\n */\nfunction goldenMin<T extends { error: number }>(f: (x: number) => T, a: number, b: number): T {\n\tconst R = 0.618033988749895;\n\tlet x1 = b - R * (b - a);\n\tlet x2 = a + R * (b - a);\n\tlet f1 = f(x1);\n\tlet f2 = f(x2);\n\tfor (let i = 0; i < 40; i++) {\n\t\tif (f1.error < f2.error || Number.isNaN(f2.error)) {\n\t\t\tb = x2;\n\t\t\tx2 = x1;\n\t\t\tf2 = f1;\n\t\t\tx1 = b - R * (b - a);\n\t\t\tf1 = f(x1);\n\t\t} else {\n\t\t\ta = x1;\n\t\t\tx1 = x2;\n\t\t\tf1 = f2;\n\t\t\tx2 = a + R * (b - a);\n\t\t\tf2 = f(x2);\n\t\t}\n\t}\n\treturn f1.error <= f2.error ? f1 : f2;\n}\n\n/**\n * One cubic through `pts`: `N + 1` samples of a left curve then `N` of a right one. The new\n * curve passes their joint at some parameter k: the left samples sit at k·i/N, the right ones\n * at k + (1 − k)·i/N. Searches k for the closest fit.\n */\nfunction fitAcross(pts: Point[], N: number, t0: Point, t1: Point) {\n\tconst fit = (k: number) => {\n\t\tconst u: number[] = [];\n\t\tfor (let i = 0; i <= N; i++) u.push((k * i) / N);\n\t\tfor (let i = 1; i <= N; i++) u.push(k + ((1 - k) * i) / N);\n\t\treturn fitCubicWithTangents(pts, u, t0, t1);\n\t};\n\treturn goldenMin(fit, 0, 1);\n}\n\n/**\n * Removes anchor `index` without opening the path: its neighbours keep their handle directions\n * and their facing handles are refitted so one cubic follows the two segments that met there\n * as closely as possible (least maximum error over where the new curve passes the removed\n * point). Two straight segments become one; removing an end of an open subpath drops its\n * segment. Returns the subpath unchanged (a copy) when `index` is out of range.\n */\nexport function removeAnchorKeepingShape(sp: VectorSubpath, index: number): VectorSubpath {\n\tconst anchors = sp.anchors.map(cloneAnchor);\n\tconst n = anchors.length;\n\tif (index < 0 || index >= n) return { anchors, closed: sp.closed };\n\tif ((sp.closed || (index > 0 && index < n - 1)) && n >= 3) {\n\t\tconst pi = (index + n - 1) % n;\n\t\tconst ni = (index + 1) % n;\n\t\tif (!(isLine(sp, pi) && isLine(sp, index))) {\n\t\t\tconst left = cubicOf(sp, pi);\n\t\t\tconst right = cubicOf(sp, index);\n\t\t\tconst N = 16;\n\t\t\tconst pts: Point[] = [];\n\t\t\tsample(left, N, pts, true);\n\t\t\tsample(right, N, pts, false);\n\t\t\tconst t0 = startTangent(left);\n\t\t\tconst t1 = endTangent(right);\n\t\t\tconst { curve } = fitAcross(pts, N, t0, t1);\n\t\t\tif ([curve.p1, curve.p2].every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]))) {\n\t\t\t\tanchors[pi] = tidy({ ...anchors[pi], out: { x: curve.p1[0], y: curve.p1[1] } });\n\t\t\t\tanchors[ni] = tidy({ ...anchors[ni], in: { x: curve.p2[0], y: curve.p2[1] } });\n\t\t\t}\n\t\t}\n\t}\n\tanchors.splice(index, 1);\n\tlet closed = sp.closed;\n\tif (!closed && anchors.length) {\n\t\t// A new end has nothing beyond it.\n\t\tanchors[0] = tidy({ ...anchors[0], in: null });\n\t\tconst last = anchors.length - 1;\n\t\tanchors[last] = tidy({ ...anchors[last], out: null });\n\t}\n\tif (anchors.length < 3) closed = closed && anchors.length === 2 && anchors.some((a) => a.in || a.out);\n\treturn { anchors, closed };\n}\n\n/** Removes several anchors of a subpath, keeping the shape (see `removeAnchorKeepingShape`). */\nexport function removeAnchorsKeepingShape(sp: VectorSubpath, indices: readonly number[]): VectorSubpath {\n\tconst order = [...new Set(indices)].filter((i) => i >= 0 && i < sp.anchors.length).sort((a, b) => b - a);\n\treturn order.reduce((acc, i) => removeAnchorKeepingShape(acc, i), { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed });\n}\n\n// ---------------------------------------------------------------------------\n// Bending a segment\n// ---------------------------------------------------------------------------\n\n/**\n * Bends segment `segment` so the curve passes through `p` at parameter `t` (clamped to\n * 0.05–0.95): its handles go to the thirds of the chord plus one shared offset, so\n * `B(t) = L(t) + 3t(1 − t)·offset` exactly. The end anchors keep their types (a smooth or\n * symmetric end turns its other handle to stay in line).\n */\nexport function bendSegment(sp: VectorSubpath, segment: number, t: number, p: Vec2): VectorSubpath {\n\tconst anchors = sp.anchors.map(cloneAnchor);\n\tconst n = anchors.length;\n\tif (segment < 0 || segment >= subpathSegmentCount(sp) || !Number.isFinite(t) || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return { anchors, closed: sp.closed };\n\tconst tc = Math.min(0.95, Math.max(0.05, t));\n\tconst ia = segment;\n\tconst ib = (segment + 1) % n;\n\tconst a = anchors[ia];\n\tconst b = anchors[ib];\n\tconst k = 3 * tc * (1 - tc);\n\tconst ox = (p.x - (a.x + (b.x - a.x) * tc)) / k;\n\tconst oy = (p.y - (a.y + (b.y - a.y) * tc)) / k;\n\tconst c1 = { x: a.x + (b.x - a.x) / 3 + ox, y: a.y + (b.y - a.y) / 3 + oy };\n\tconst c2 = { x: b.x + (a.x - b.x) / 3 + ox, y: b.y + (a.y - b.y) / 3 + oy };\n\tanchors[ia] = moveAnchorHandle(a, 'out', c1);\n\tanchors[ib] = moveAnchorHandle(b, 'in', c2);\n\treturn { anchors, closed: sp.closed };\n}\n\n/** The point at parameter `t` of segment `segment` (for tests and previews). */\nexport function subpathPointAt(sp: VectorSubpath, segment: number, t: number): Vec2 {\n\tconst [x, y] = segmentPoint(subpathSegment(sp, segment), t);\n\treturn { x, y };\n}\n\n// ---------------------------------------------------------------------------\n// Curvature-tool anchor move\n// ---------------------------------------------------------------------------\n\n/**\n * Moves anchor `index` to `q` the way the Curvature tool drags a point: only the two segments\n * at it change. A smooth or symmetric anchor turns its handles along the line through its\n * neighbours; the neighbours' handles on those segments, and a corner's own handles, keep\n * their directions while their lengths scale with their segment's chord, so the segments\n * beyond don't move and a corner without handles keeps none.\n */\nexport function curvatureMoveAnchor(sp: VectorSubpath, index: number, q: Vec2): VectorSubpath {\n\tconst anchors = sp.anchors.map(cloneAnchor);\n\tconst a = sp.anchors[index];\n\tif (!a) return { anchors, closed: sp.closed };\n\tconst own: [Vec2 | null, Vec2 | null] = [\n\t\ta.in && !onAnchor(a, a.in) ? { x: a.in.x - a.x, y: a.in.y - a.y } : null,\n\t\ta.out && !onAnchor(a, a.out) ? { x: a.out.x - a.x, y: a.out.y - a.y } : null\n\t];\n\tconst ends: [Vec2 | null, Vec2 | null] = [null, null];\n\tneighbours(sp, index).forEach((j, side) => {\n\t\tif (j < 0 || j === index) return;\n\t\tconst b = anchors[j];\n\t\tconst old = Math.hypot(a.x - b.x, a.y - b.y);\n\t\tconst s = old > 1e-9 ? Math.hypot(q.x - b.x, q.y - b.y) / old : 1;\n\t\tconst key = side === 0 ? 'out' : 'in';\n\t\tconst h = b[key];\n\t\tif (h) b[key] = { x: b.x + (h.x - b.x) * s, y: b.y + (h.y - b.y) * s };\n\t\tconst o = own[side];\n\t\tif (o) own[side] = { x: o.x * s, y: o.y * s };\n\t\tends[side] = { x: b.x, y: b.y };\n\t});\n\tconst moved: VectorAnchor = { x: q.x, y: q.y };\n\tif (own[0]) moved.in = { x: q.x + own[0].x, y: q.y + own[0].y };\n\tif (own[1]) moved.out = { x: q.x + own[1].x, y: q.y + own[1].y };\n\tif (a.type) moved.type = a.type;\n\tif (a.corner) moved.corner = { ...a.corner };\n\tconst [prev, next] = ends;\n\tif (a.type && a.type !== 'corner' && prev && next) {\n\t\tconst dx = next.x - prev.x;\n\t\tconst dy = next.y - prev.y;\n\t\tconst l = Math.hypot(dx, dy);\n\t\tif (l > 1e-9) {\n\t\t\t// A handle it lacks comes out a third of the way to that side's neighbour.\n\t\t\tconst len = (h: Vec2 | null, to: Vec2) => (h ? Math.hypot(h.x, h.y) : Math.hypot(q.x - to.x, q.y - to.y) / 3);\n\t\t\tlet li = len(own[0], prev);\n\t\t\tlet lo = len(own[1], next);\n\t\t\tif (a.type === 'symmetric') li = lo = (li + lo) / 2;\n\t\t\tmoved.in = { x: q.x - (dx * li) / l, y: q.y - (dy * li) / l };\n\t\t\tmoved.out = { x: q.x + (dx * lo) / l, y: q.y + (dy * lo) / l };\n\t\t}\n\t}\n\tanchors[index] = moved;\n\treturn { anchors, closed: sp.closed };\n}\n\n// ---------------------------------------------------------------------------\n// Scissors\n// ---------------------------------------------------------------------------\n\n/**\n * Cuts a subpath at the anchors `at`: each becomes the end of one open piece and the start of\n * the next, its handle off each piece dropped (the shape doesn't change). A closed subpath\n * opens at its first cut, so one cut leaves one piece whose ends coincide. The ends of an open\n * subpath and indices out of range don't cut; with no cut left the subpath comes back as it is.\n */\nexport function cutSubpathAt(sp: VectorSubpath, at: readonly number[]): VectorSubpath[] {\n\tconst n = sp.anchors.length;\n\tconst cuts = [...new Set(at)].filter((i) => Number.isInteger(i) && i >= 0 && i < n && (sp.closed || (i > 0 && i < n - 1))).sort((a, b) => a - b);\n\tif (!cuts.length || n < 2) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];\n\tconst first = cuts[0];\n\t// A closed subpath's anchors from the first cut round to it again; an open one's as they are.\n\tconst run = sp.closed ? Array.from({ length: n + 1 }, (_, i) => sp.anchors[(first + i) % n]) : sp.anchors;\n\tconst cutSet = new Set(sp.closed ? [...cuts.map((c) => (c - first + n) % n), n] : cuts);\n\tconst pieces: VectorSubpath[] = [];\n\tlet cur: VectorAnchor[] = [];\n\trun.forEach((a, i) => {\n\t\tif (!cutSet.has(i)) {\n\t\t\tcur.push(cloneAnchor(a));\n\t\t\treturn;\n\t\t}\n\t\tif (cur.length) {\n\t\t\tcur.push(tidy({ ...a, out: null }, false));\n\t\t\tpieces.push({ anchors: cur, closed: false });\n\t\t\tcur = [];\n\t\t}\n\t\tif (i < n) cur.push(tidy({ ...a, in: null }, false));\n\t});\n\tif (cur.length > 1) pieces.push({ anchors: cur, closed: false });\n\treturn pieces;\n}\n\n/**\n * Cuts a subpath at parameter `t` of segment `segment` (Scissors on a segment): an anchor is\n * inserted there without changing the shape, then cut. Within `endTolerance` of the segment's\n * ends (in parameter) the existing anchor is cut instead.\n */\nexport function splitSubpathAt(sp: VectorSubpath, segment: number, t: number, endTolerance = 1e-6): VectorSubpath[] {\n\tconst n = sp.anchors.length;\n\tif (segment < 0 || segment >= subpathSegmentCount(sp)) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];\n\tif (t <= endTolerance) return cutSubpathAt(sp, [segment]);\n\tif (t >= 1 - endTolerance) return cutSubpathAt(sp, [(segment + 1) % n]);\n\tconst { subpath, index } = insertAnchor(sp, segment, t);\n\treturn cutSubpathAt(subpath, [index]);\n}\n\n// ---------------------------------------------------------------------------\n// Join\n// ---------------------------------------------------------------------------\n\nconst endOf = (sp: VectorSubpath, last: boolean): VectorAnchor => sp.anchors[last ? sp.anchors.length - 1 : 0];\nconst dist = (a: Vec2, b: Vec2) => Math.hypot(a.x - b.x, a.y - b.y);\n\nfunction reverse(sp: VectorSubpath): VectorSubpath {\n\treturn {\n\t\tclosed: sp.closed,\n\t\tanchors: sp.anchors\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.map((a) => {\n\t\t\t\tconst next: VectorAnchor = { x: a.x, y: a.y };\n\t\t\t\tif (a.out) next.in = { ...a.out };\n\t\t\t\tif (a.in) next.out = { ...a.in };\n\t\t\t\tif (a.type) next.type = a.type;\n\t\t\t\tif (a.corner) next.corner = { ...a.corner };\n\t\t\t\treturn next;\n\t\t\t})\n\t};\n}\n\n/**\n * Join: open subpaths are joined nearest-endpoints-first into one open subpath (endpoints\n * within `tolerance` merge into one anchor, otherwise a straight segment connects them). A\n * single open subpath is closed instead (its ends merged when they meet). Closed subpaths and\n * lone anchors pass through unchanged, before the joined one.\n */\nexport function joinSubpaths(subpaths: readonly VectorSubpath[], tolerance: number): VectorSubpath[] {\n\tconst closed: VectorSubpath[] = [];\n\tlet open: VectorSubpath[] = [];\n\tfor (const sp of subpaths) {\n\t\tconst copy = { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };\n\t\tif (sp.closed || sp.anchors.length < 2) {\n\t\t\tif (sp.anchors.length) closed.push(copy);\n\t\t} else open.push(copy);\n\t}\n\tif (open.length === 1) {\n\t\tconst sp = open[0];\n\t\tconst first = sp.anchors[0];\n\t\tconst last = sp.anchors[sp.anchors.length - 1];\n\t\tif (sp.anchors.length > 2 && dist(first, last) <= tolerance) {\n\t\t\tsp.anchors.pop();\n\t\t\tsp.anchors[0] = tidy({ ...first, in: last.in ?? null }, false);\n\t\t} else {\n\t\t\tsp.anchors[0] = tidy({ ...first, in: null }, false);\n\t\t\tsp.anchors[sp.anchors.length - 1] = tidy({ ...sp.anchors[sp.anchors.length - 1], out: null }, false);\n\t\t}\n\t\tsp.closed = true;\n\t\treturn [...closed, sp];\n\t}\n\twhile (open.length > 1) {\n\t\t// The closest pair of endpoints on different subpaths.\n\t\tlet best = { d: Infinity, i: 0, j: 1, ei: true, ej: false };\n\t\tfor (let i = 0; i < open.length; i++) {\n\t\t\tfor (let j = i + 1; j < open.length; j++) {\n\t\t\t\tfor (const ei of [false, true]) {\n\t\t\t\t\tfor (const ej of [false, true]) {\n\t\t\t\t\t\tconst d = dist(endOf(open[i], ei), endOf(open[j], ej));\n\t\t\t\t\t\tif (d < best.d) best = { d, i, j, ei, ej };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlet a = open[best.i];\n\t\tlet b = open[best.j];\n\t\topen = open.filter((_, k) => k !== best.j);\n\t\t// `a` ends at the meeting point, `b` starts there.\n\t\tif (!best.ei) a = reverse(a);\n\t\tif (best.ej) b = reverse(b);\n\t\tconst anchors = a.anchors;\n\t\tif (best.d <= tolerance) {\n\t\t\tconst [head, ...rest] = b.anchors;\n\t\t\tconst l = anchors[anchors.length - 1];\n\t\t\tanchors[anchors.length - 1] = tidy({ ...l, out: head.out ?? null }, false);\n\t\t\tanchors.push(...rest);\n\t\t} else {\n\t\t\tconst l = anchors[anchors.length - 1];\n\t\t\tanchors[anchors.length - 1] = tidy({ ...l, out: null }, false);\n\t\t\tanchors.push(tidy({ ...b.anchors[0], in: null }, false), ...b.anchors.slice(1));\n\t\t}\n\t\topen[best.i > best.j ? best.i - 1 : best.i] = { anchors, closed: false };\n\t}\n\treturn [...closed, ...open];\n}\n\n// ---------------------------------------------------------------------------\n// Smooth and Remove Redundant Points\n// ---------------------------------------------------------------------------\n\n/**\n * Smooth: pulls handles towards Catmull-Rom-like tangents (a third of the way to each\n * neighbour along the line through both). `amount` 0–1 (0 = unchanged, 1 = fully smooth).\n * Only the anchors in `only` change when it is given; endpoints of open subpaths keep their\n * handles. Subpaths of fewer than three anchors are unchanged.\n */\nexport function smoothSubpath(sp: VectorSubpath, amount = 1, only?: ReadonlySet<number>): VectorSubpath {\n\tconst t = Math.min(1, Math.max(0, amount));\n\tconst orig = sp.anchors;\n\tconst n = orig.length;\n\tconst anchors = orig.map(cloneAnchor);\n\tif (n < 3 || t === 0) return { anchors, closed: sp.closed };\n\tfor (let i = 0; i < n; i++) {\n\t\tif (!sp.closed && (i === 0 || i === n - 1)) continue;\n\t\tif (only && !only.has(i)) continue;\n\t\tconst prev = orig[(i + n - 1) % n];\n\t\tconst next = orig[(i + 1) % n];\n\t\tconst p = orig[i];\n\t\tconst dir = unit(next.x - prev.x, next.y - prev.y);\n\t\tif (!dir) continue;\n\t\tconst li = dist(p, prev) / 3;\n\t\tconst lo = dist(p, next) / 3;\n\t\tconst hin = p.in ?? p;\n\t\tconst hout = p.out ?? p;\n\t\tconst target = (h: Vec2, x: number, y: number) => ({ x: h.x + (x - h.x) * t, y: h.y + (y - h.y) * t });\n\t\tanchors[i] = tidy(\n\t\t\t{\n\t\t\t\tx: p.x,\n\t\t\t\ty: p.y,\n\t\t\t\tin: target(hin, p.x - dir[0] * li, p.y - dir[1] * li),\n\t\t\t\tout: target(hout, p.x + dir[0] * lo, p.y + dir[1] * lo)\n\t\t\t},\n\t\t\tfalse\n\t\t);\n\t}\n\treturn { anchors, closed: sp.closed };\n}\n\ninterface Seg {\n\tc: Point[];\n\tline: boolean;\n}\n\nfunction segsOf(sp: VectorSubpath): Seg[] {\n\tconst out: Seg[] = [];\n\tfor (let i = 0; i < subpathSegmentCount(sp); i++) {\n\t\tconst s = subpathSegment(sp, i);\n\t\tout.push(s.length === 2 ? { c: s, line: true } : { c: s, line: false });\n\t}\n\treturn out;\n}\n\n/** A subpath from consecutive segments (anchor types inferred; original types kept where they still hold). */\nfunction segsToSubpath(segs: readonly Seg[], closed: boolean, types: Map<string, VectorAnchor['type']>): VectorSubpath | null {\n\tif (!segs.length) return null;\n\tconst anchors: VectorAnchor[] = [];\n\tconst at = (p: Point): VectorAnchor => ({ x: p[0], y: p[1] });\n\tanchors.push(at(segs[0].c[0]));\n\tfor (const s of segs) {\n\t\tconst end = s.c[s.c.length - 1];\n\t\tif (!s.line) anchors[anchors.length - 1].out = { x: s.c[1][0], y: s.c[1][1] };\n\t\tconst next = at(end);\n\t\tif (!s.line) next.in = { x: s.c[2][0], y: s.c[2][1] };\n\t\tanchors.push(next);\n\t}\n\tif (closed) {\n\t\tconst last = anchors.pop()!;\n\t\tif (last.in) anchors[0].in = last.in;\n\t}\n\tconst typed = anchors.map((a) => {\n\t\tconst type = types.get(`${a.x},${a.y}`);\n\t\treturn tidy(type ? { ...a, type } : a, !!type);\n\t});\n\treturn { anchors: typed, closed };\n}\n\nconst isZeroSeg = (s: Seg) => s.c.every((p) => Math.abs(p[0] - s.c[0][0]) < 1e-9 && Math.abs(p[1] - s.c[0][1]) < 1e-9);\n\n/** One segment replacing `a` then `b` within `tol`, or null. */\nfunction mergePair(a: Seg, b: Seg, tol: number): Seg | null {\n\tconst a0 = a.c[0];\n\tconst a3 = a.c[a.c.length - 1];\n\tconst b3 = b.c[b.c.length - 1];\n\tif (a.line && b.line) {\n\t\tconst cx = b3[0] - a0[0];\n\t\tconst cy = b3[1] - a0[1];\n\t\tconst len = Math.hypot(cx, cy);\n\t\tif (len < 1e-12) return null;\n\t\tconst mx = a3[0] - a0[0];\n\t\tconst my = a3[1] - a0[1];\n\t\tconst along = (mx * cx + my * cy) / len;\n\t\treturn Math.abs((mx * cy - my * cx) / len) <= tol && along >= 0 && along <= len ? { c: [a0, b3], line: true } : null;\n\t}\n\tconst ca = a.line ? [a0, a0, a3, a3] : a.c;\n\tconst cb = b.line ? [a3, a3, b3, b3] : b.c;\n\tconst ta = endTangent(ca);\n\tconst tb = startTangent(cb);\n\t// Only merge across (near-)smooth joints.\n\tif (ta[0] * tb[0] + ta[1] * tb[1] < 0.99) return null;\n\tconst pts: Point[] = [];\n\tsample(ca, 16, pts, true);\n\tsample(cb, 16, pts, false);\n\tconst { curve, error } = fitAcross(pts, 16, startTangent(ca), endTangent(cb));\n\treturn error <= tol ? { c: [curve.p0, curve.p1, curve.p2, curve.p3], line: false } : null;\n}\n\n/**\n * Remove Redundant Points: drops anchors that can go without changing the shape by more than\n * `tolerance` (collinear corners, duplicate points, and smooth joints that one cubic can\n * replace).\n */\nexport function removeRedundantPoints(sp: VectorSubpath, tolerance: number): VectorSubpath {\n\tconst tol = Math.max(tolerance, 1e-9);\n\tconst types = new Map(sp.anchors.filter((a) => a.type).map((a) => [`${a.x},${a.y}`, a.type] as const));\n\tconst segs = segsOf(sp).filter((s) => !isZeroSeg(s));\n\tif (!segs.length) return { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };\n\tconst minSegs = sp.closed ? 2 : 1;\n\tlet changed = true;\n\twhile (changed && segs.length > minSegs) {\n\t\tchanged = false;\n\t\tconst n = segs.length;\n\t\tconst joints = sp.closed ? n : n - 1;\n\t\tfor (let j = 0; j < joints; j++) {\n\t\t\tconst [i0, i1] = sp.closed ? [(j + n - 1) % n, j] : [j, j + 1];\n\t\t\tconst m = mergePair(segs[i0], segs[i1], tol);\n\t\t\tif (!m) continue;\n\t\t\tsegs[i0] = m;\n\t\t\tsegs.splice(i1, 1);\n\t\t\t// The merged segment now starts the loop.\n\t\t\tif (sp.closed && i1 === 0) segs.unshift(segs.pop()!);\n\t\t\tchanged = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn segsToSubpath(segs, sp.closed, types) ?? { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };\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.\n */\nexport function eraseFromOutline(sp: VectorSubpath, region: BezierPathInput): 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 removed = (p: Point) => pointInBezierPath(region, p, 'nonzero');\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 * Pencil helpers: lazy-mouse smoothing (a pulled string between the pointer and the pen) and\n * ink-to-shape recognition (a hand-drawn stroke that is a line, rectangle, ellipse or polygon).\n *\n * Portions ported from (MIT OR Apache-2.0, © ArtCraft Team and contributors; see\n * THIRD_PARTY_NOTICES.md):\n * - photocraft-main/crates/paint/src/dynamics.rs (`Smoother`: pulled-string mode, `string`,\n * `finish` with catch-up on stroke end)\n * - wordcraft-main/crates/engine/src/cmd/ink_shape.rs (`recognise`, `ellipse_rms`,\n * `snap_square`, `corners`, `turning`, `rdp`, `upright`)\n */\nimport type { Point } from '../canvas/types.js';\n\n// ---------------------------------------------------------------------------\n// Lazy mouse\n// ---------------------------------------------------------------------------\n\n/**\n * Pulled-string smoothing: the pen stays put until the pointer is more than `length` away,\n * then follows it at that distance, so small wobbles never reach the stroke. Feed pointer\n * positions to `push`; it returns the pen positions to draw through (possibly none).\n */\nexport class LazyMouse {\n\tprivate pos: Point | null = null;\n\tprivate last: Point | null = null;\n\n\t/** `length` is the string length in the units of the points (0 turns smoothing off). */\n\tconstructor(readonly length: number) {}\n\n\tpush(p: Point): Point[] {\n\t\tthis.last = p;\n\t\tif (!(this.length > 0)) return [p];\n\t\tconst pos = this.pos;\n\t\tif (!pos) {\n\t\t\tthis.pos = p;\n\t\t\treturn [p];\n\t\t}\n\t\tconst dx = p[0] - pos[0];\n\t\tconst dy = p[1] - pos[1];\n\t\tconst d = Math.hypot(dx, dy);\n\t\tif (d <= this.length) return [];\n\t\tconst k = (d - this.length) / d;\n\t\tconst next: Point = [pos[0] + dx * k, pos[1] + dy * k];\n\t\tthis.pos = next;\n\t\treturn [next];\n\t}\n\n\t/** The string from the pen to the pointer while they are apart, else null. */\n\tstring(): [Point, Point] | null {\n\t\tconst { pos, last } = this;\n\t\tif (!(this.length > 0) || !pos || !last || (pos[0] === last[0] && pos[1] === last[1])) return null;\n\t\treturn [pos, last];\n\t}\n\n\t/** End of stroke: the pen catches up with the pointer (the point to add, if it lagged). */\n\tfinish(): Point[] {\n\t\tconst { pos, last } = this;\n\t\tif (!(this.length > 0) || !pos || !last || (pos[0] === last[0] && pos[1] === last[1])) return [];\n\t\tthis.pos = last;\n\t\treturn [last];\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Ink to shape\n// ---------------------------------------------------------------------------\n\n/** A shape recognised in a stroke, in the stroke's coordinates. */\nexport type VectorInkShape =\n\t| { kind: 'line'; a: Point; b: Point }\n\t| { kind: 'rect'; x: number; y: number; width: number; height: number }\n\t| { kind: 'ellipse'; x: number; y: number; width: number; height: number }\n\t| { kind: 'polygon'; points: Point[] };\n\nexport interface RecognizeInkOptions {\n\t/** Strokes whose longer side is smaller than this stay ink (default 6). */\n\tminSize?: number;\n}\n\n/** Most corners a recognised polygon may have. */\nexport const VECTOR_INK_MAX_CORNERS = 8;\nconst MAX_POINTS = 100_000;\n\n/** \"line\", \"square\", \"rectangle\", \"circle\", \"ellipse\", \"triangle\" or \"polygon\". */\nexport function vectorInkShapeName(shape: VectorInkShape): string {\n\tswitch (shape.kind) {\n\t\tcase 'line':\n\t\t\treturn 'line';\n\t\tcase 'rect':\n\t\t\treturn shape.width === shape.height ? 'square' : 'rectangle';\n\t\tcase 'ellipse':\n\t\t\treturn shape.width === shape.height ? 'circle' : 'ellipse';\n\t\tcase 'polygon':\n\t\t\treturn shape.points.length === 3 ? 'triangle' : 'polygon';\n\t}\n}\n\nconst dist = (a: Point, b: Point) => Math.hypot(b[0] - a[0], b[1] - a[1]);\n\nfunction bbox(pts: readonly Point[]): [number, number, number, number] {\n\tif (!pts.length) return [0, 0, 0, 0];\n\tlet [x0, y0] = pts[0];\n\tlet [x1, y1] = pts[0];\n\tfor (const [x, y] of pts) {\n\t\tx0 = Math.min(x0, x);\n\t\ty0 = Math.min(y0, y);\n\t\tx1 = Math.max(x1, x);\n\t\ty1 = Math.max(y1, y);\n\t}\n\treturn [x0, y0, x1 - x0, y1 - y0];\n}\n\nfunction segDistance(a: Point, b: Point, p: Point): number {\n\tconst dx = b[0] - a[0];\n\tconst dy = b[1] - a[1];\n\tconst len2 = dx * dx + dy * dy;\n\tconst t = len2 > 0 ? Math.min(1, Math.max(0, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2)) : 0;\n\treturn dist([a[0] + t * dx, a[1] + t * dy], p);\n}\n\nfunction polygonDistance(c: readonly Point[], p: Point): number {\n\tlet best = Infinity;\n\tfor (let i = 0; i < c.length; i++) best = Math.min(best, segDistance(c[i], c[(i + 1) % c.length], p));\n\treturn best;\n}\n\n/** RMS of each point's normalised radius minus one on the ellipse inscribed in the box (0 on it). */\nfunction ellipseRms(p: readonly Point[], x0: number, y0: number, w: number, h: number): number {\n\tconst rx = w / 2;\n\tconst ry = h / 2;\n\tif (rx <= 0 || ry <= 0 || !p.length) return Infinity;\n\tconst cx = x0 + rx;\n\tconst cy = y0 + ry;\n\tlet sum = 0;\n\tfor (const [x, y] of p) sum += (Math.hypot((x - cx) / rx, (y - cy) / ry) - 1) ** 2;\n\treturn Math.sqrt(sum / p.length);\n}\n\n/** A box drawn nearly square made square (same centre). */\nfunction snapSquare(x: number, y: number, w: number, h: number): [number, number, number, number] {\n\tif (Math.abs(w - h) <= 0.12 * Math.max(w, h)) {\n\t\tconst s = (w + h) / 2;\n\t\treturn [x + (w - s) / 2, y + (h - s) / 2, s, s];\n\t}\n\treturn [x, y, w, h];\n}\n\n/** How far the path turns at `b` coming from `a` towards `c`, in degrees (0 = straight on). */\nfunction turning(a: Point, b: Point, c: Point): number {\n\tconst u = [b[0] - a[0], b[1] - a[1]];\n\tconst v = [c[0] - b[0], c[1] - b[1]];\n\tconst lu = Math.hypot(u[0], u[1]);\n\tconst lv = Math.hypot(v[0], v[1]);\n\tif (lu <= 0 || lv <= 0) return 0;\n\treturn (Math.acos(Math.min(1, Math.max(-1, (u[0] * v[0] + u[1] * v[1]) / (lu * lv)))) * 180) / Math.PI;\n}\n\n/** Ramer–Douglas–Peucker reduction of an open polyline (iterative), keeping both ends. */\nfunction rdp(p: readonly Point[], eps: number): Point[] {\n\tconst n = p.length;\n\tif (n < 3) return p.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\tlet far = a;\n\t\tlet farD = 0;\n\t\tfor (let i = a + 1; i < b; i++) {\n\t\t\tconst d = segDistance(p[a], p[b], p[i]);\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 (farD > eps) {\n\t\t\tkeep[far] = true;\n\t\t\tstack.push([a, far], [far, b]);\n\t\t}\n\t}\n\treturn p.filter((_, i) => keep[i]);\n}\n\n/** Corners of the closed path through `p`: RDP split at the point farthest from the start, then flat and crowded corners merged away. */\nfunction corners(p: readonly Point[], diag: number): Point[] {\n\tconst eps = 0.05 * diag;\n\tconst start = p[0];\n\tlet far = 0;\n\tlet farD = 0;\n\tp.forEach((q, i) => {\n\t\tconst d = dist(start, q);\n\t\tif (d > farD) {\n\t\t\tfarD = d;\n\t\t\tfar = i;\n\t\t}\n\t});\n\tconst a = p.slice(0, far);\n\tconst b = p.slice(far);\n\tconst ring = rdp([...a, ...b.slice(0, 1)], eps);\n\tconst back = rdp([...b, start], eps);\n\t// The halves share the far point and the start.\n\tring.pop();\n\tback.pop();\n\tring.push(...back);\n\twhile (ring.length > 3) {\n\t\tconst n = ring.length;\n\t\tconst turn = (i: number) => turning(ring[(i + n - 1) % n], ring[i], ring[(i + 1) % n]);\n\t\tlet flat = 0;\n\t\tlet flatT = Infinity;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst t = turn(i);\n\t\t\tif (t < flatT) {\n\t\t\t\tflatT = t;\n\t\t\t\tflat = i;\n\t\t\t}\n\t\t}\n\t\tlet short = -1;\n\t\tfor (let j = 0; j < n; j++) {\n\t\t\tif (dist(ring[j], ring[(j + 1) % n]) < 0.1 * diag) {\n\t\t\t\tshort = j;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (flatT < 30) ring.splice(flat, 1);\n\t\telse if (short >= 0) {\n\t\t\t// Of the two crowded corners keep the sharper.\n\t\t\tconst k = turn(short) < turn((short + 1) % n) ? short : (short + 1) % n;\n\t\t\tring.splice(k, 1);\n\t\t} else break;\n\t}\n\treturn ring;\n}\n\n/** A quadrilateral whose sides run (nearly) across and down, alternately. */\nfunction upright(c: readonly Point[]): boolean {\n\tconst n = c.length;\n\tconst dirs = c.map((a, i) => {\n\t\tconst b = c[(i + 1) % n];\n\t\tconst deg = ((((Math.atan2(b[1] - a[1], b[0] - a[0]) * 180) / Math.PI) % 180) + 180) % 180;\n\t\tif (deg <= 15 || deg >= 165) return 'across';\n\t\tif (deg >= 75 && deg <= 105) return 'down';\n\t\treturn null;\n\t});\n\treturn dirs.every((d) => d !== null) && dirs.every((d, i) => d !== dirs[(i + 1) % n]);\n}\n\n/**\n * The shape a hand-drawn stroke draws, if it is one: an open stroke is a line when it stays\n * close to the chord between its ends; a closed one is an ellipse when its points sit on the\n * ellipse inscribed in their bounds, else a polygon of 3–8 corners when that polygon stays\n * close to every point (an upright four-cornered one is a rectangle). Nearly square boxes\n * become squares and circles. Anything else (scribbles, curves, letters) is null.\n */\nexport function recognizeInkShape(points: readonly Point[], options: RecognizeInkOptions = {}): VectorInkShape | null {\n\tconst minSize = options.minSize ?? 6;\n\tconst p: Point[] = [];\n\tfor (const q of points.slice(0, MAX_POINTS)) {\n\t\tif (!Number.isFinite(q[0]) || !Number.isFinite(q[1]) || Math.abs(q[0]) >= 1e9 || Math.abs(q[1]) >= 1e9) continue;\n\t\tconst last = p[p.length - 1];\n\t\tif (!last || last[0] !== q[0] || last[1] !== q[1]) p.push([q[0], q[1]]);\n\t}\n\tif (p.length < 2) return null;\n\tconst first = p[0];\n\tconst last = p[p.length - 1];\n\tconst [x0, y0, w, h] = bbox(p);\n\tconst size = Math.max(w, h);\n\tconst diag = Math.hypot(w, h);\n\tif (size < minSize) return null;\n\tlet length = 0;\n\tfor (let i = 1; i < p.length; i++) length += dist(p[i - 1], p[i]);\n\tconst gap = dist(first, last);\n\t// A line: the stroke barely leaves the chord between its ends and doesn't double back.\n\tif (gap > 0.85 * length && gap >= minSize) {\n\t\tconst off = Math.max(...p.map((q) => segDistance(first, last, q)));\n\t\tif (off <= Math.max(0.06 * gap, (1.5 * minSize) / 6)) return { kind: 'line', a: first, b: last };\n\t}\n\t// Other shapes are closed: the stroke ends near where it began and goes around.\n\tif (gap > 0.2 * size || length < 2 * size) return null;\n\tconst rms = ellipseRms(p, x0, y0, w, h);\n\tconst roundish = w > 0.2 * size && h > 0.2 * size;\n\tconst ellipse = (): VectorInkShape => {\n\t\tconst [x, y, sw, sh] = snapSquare(x0, y0, w, h);\n\t\treturn { kind: 'ellipse', x, y, width: sw, height: sh };\n\t};\n\tif (roundish && rms < 0.06) return ellipse();\n\tconst c = corners(p, diag);\n\tconst fits = c.length >= 3 && p.every((q) => polygonDistance(c, q) <= 0.08 * diag);\n\tif (fits && c.length === 4 && upright(c)) {\n\t\tconst [bx, by, bw, bh] = bbox(c);\n\t\tconst [x, y, sw, sh] = snapSquare(bx, by, bw, bh);\n\t\treturn { kind: 'rect', x, y, width: sw, height: sh };\n\t}\n\tif (fits && c.length <= VECTOR_INK_MAX_CORNERS) return { kind: 'polygon', points: c };\n\t// A wobbly round stroke is still an ellipse.\n\treturn roundish && rms < 0.1 ? ellipse() : null;\n}\n","/**\n * Pathfinder operands, the Shape Builder and fill regions over a vector document.\n *\n * Everything works in document space: each path is mapped through its own transform and its\n * parents', with live corners cut (`subpathsToBezier`), so rotated nodes and nodes inside\n * rotated groups take part as drawn. The controller maps results back into the space of the node\n * that receives them.\n *\n * - **Pathfinder operands**: a filled path is its fill area (every subpath closed); an unfilled\n * path with a stroke is the area its stroke paints (alignment, caps, joins and arrowheads\n * included) and results from it are filled with the stroke's paint; for Divide an unfilled open\n * path is a cutting line instead.\n * - **Shape Builder**: the planar map of the selected paths (`PlanarMap`). Closed subpaths bound\n * faces whatever their paint (a stroke-only shape's inside is a face), filled open subpaths that\n * enclose an area are closed by an invisible edge, and unfilled open paths cut the faces they\n * cross; their pieces between junctions can be erased. Merging faces unites them along the\n * map's own edges and takes the area away from every shape covering them. Erasing (Alt) also\n * takes the edges of the other outlines, cut where paths meet: a closed subpath opens there,\n * an open one splits, and an edge bounding a face deleted in the same gesture goes with it.\n * - **Fill regions** (paint bucket): the planar map of every visible outline; the region around a\n * point is traced from a ray (`PlanarMap.regionAt`).\n *\n * The Shape Builder follows vectorcraft-main/crates/tools/src/builder.rs (`BuilderMap`, `hit`,\n * `sides`, `line_between`, `erased_edges`, `Hit::Edge`, `shape_builder_shapes`, `sample_polyline`)\n * and crates/engine/src/cmd/buildcmds.rs (`sb_merge`), with figcraft-main/crates/engine/src/shapebuilder.rs\n * (strokes as their outlined area). MIT OR Apache-2.0, © ArtCraft Team and contributors. See\n * THIRD_PARTY_NOTICES.md.\n */\nimport type { Point } from '../canvas/types.js';\nimport {\n\tbezierPathBounds,\n\tcontourSignedArea,\n\tcloseContour,\n\tdistanceToBezierPath,\n\tpathToContours,\n\tsegmentPoint,\n\tsegmentTangent,\n\tcontoursToPath,\n\ttype BezierPath,\n\ttype BezierSegment\n} from '../canvas/pathops/bezier.js';\nimport { PlanarMap, cutOutPiece, type PlanarChain } from '../canvas/pathops/planar.js';\nimport type { PathfinderOperation, PathfinderShape } from '../canvas/pathops/pathfinder.js';\nimport { hasPaint, multiplyMatrices, nodeMatrix, subpathsToBezier, vectorPathWorldBezier, vectorStrokeOutline, IDENTITY_MATRIX, type AffineMatrix } from './geometry.js';\nimport { expandVectorAppearance } from './appearance.js';\nimport type { Vec2, VectorDocument, VectorNode, VectorPaint, VectorPathNode, VectorSubpath } from './types.js';\n\n/** A path node with the matrix of its parent space (parent → document). */\nexport interface VectorPathEntry {\n\tnode: VectorPathNode;\n\tparent: AffineMatrix;\n}\n\n/**\n * The path nodes inside `roots` (or every node when `roots` is null), bottom to top, with their\n * parent matrices. Hidden nodes are skipped, and locked ones unless `locked` is true. Live nodes\n * (path effects, live booleans) take part as drawn (`expandVectorAppearance`: their entries are\n * the expanded paths, whose ids may not be in the document) unless `expand` is false.\n */\nexport function vectorPathEntries(doc: VectorDocument, roots: readonly string[] | null, options: { locked?: boolean; expand?: boolean } = {}): VectorPathEntry[] {\n\tconst out: VectorPathEntry[] = [];\n\tconst visit = (stored: VectorNode, parent: AffineMatrix, inside: boolean) => {\n\t\tif (stored.visible === false || (stored.locked && !options.locked)) return;\n\t\tconst node = options.expand === false ? stored : expandVectorAppearance(stored);\n\t\tconst picked = inside || roots === null || roots.includes(node.id);\n\t\tif (node.type === 'group') {\n\t\t\tconst m = multiplyMatrices(nodeMatrix(node), parent);\n\t\t\tfor (const c of node.children) visit(c, m, picked);\n\t\t} else if (picked) out.push({ node, parent });\n\t};\n\tfor (const n of doc.nodes) visit(n, IDENTITY_MATRIX, false);\n\treturn out;\n}\n\nconst usable = (sp: VectorSubpath) => sp.anchors.length > 1;\n\n/** The entry's outline in document space with the given subpaths. */\nfunction worldOf(entry: VectorPathEntry, subpaths: VectorSubpath[]): BezierPath {\n\treturn vectorPathWorldBezier({ ...entry.node, subpaths }, entry.parent);\n}\n\n/** Whether a subpath, closed, encloses an area (rather than retracing itself). */\nexport function subpathEnclosesArea(sp: VectorSubpath): boolean {\n\tconst contour = pathToContours(subpathsToBezier([{ ...sp, closed: true }]))[0];\n\tif (!contour) return false;\n\tconst segs = closeContour(contour, true);\n\tconst b = bezierPathBounds(contoursToPath([{ segments: segs, closed: true }]));\n\tif (!b) return false;\n\tconst w = b.maxX - b.minX;\n\tconst h = b.maxY - b.minY;\n\treturn Math.abs(contourSignedArea(segs)) > 1e-9 * (w * w + h * h);\n}\n\n/** How an operand takes part in a Pathfinder operation: its fill, its stroke's area, a cutting line or its bare outline. */\nexport type VectorPathfinderPaint = 'fill' | 'stroke' | 'line' | 'outline';\n\nconst paintKey = (paint: VectorPaint | undefined) => (typeof paint === 'string' ? paint.trim().toLowerCase() : JSON.stringify(paint ?? null));\n\n/** The Pathfinder shape of a path (document space), and what it stands for. */\nexport function vectorPathfinderShape(entry: VectorPathEntry, op: PathfinderOperation): PathfinderShape & { paint: VectorPathfinderPaint } {\n\tconst { node } = entry;\n\tconst subpaths = node.subpaths.filter(usable);\n\tconst closed = subpaths.map((s) => ({ ...s, closed: true }));\n\tconst fillRule = node.fillRule ?? 'nonzero';\n\tif (hasPaint(node.fill)) return { path: worldOf(entry, closed), fillRule, key: `fill:${paintKey(node.fill)}`, paint: 'fill' };\n\tconst open = subpaths.length > 0 && subpaths.every((s) => !s.closed);\n\tif (op === 'divide' && open) return { path: worldOf(entry, subpaths), line: true, paint: 'line' };\n\tif (hasPaint(node.stroke) && (node.strokeWidth ?? 1) > 0) {\n\t\t// Node transforms are rigid, so the stroke laid out in document space keeps its width.\n\t\tconst path = vectorStrokeOutline({ ...node, subpaths }, entry.parent);\n\t\treturn { path, fillRule: 'nonzero', key: `fill:${paintKey(node.stroke)}`, paint: 'stroke' };\n\t}\n\treturn { path: worldOf(entry, closed), fillRule, key: 'none', paint: 'outline' };\n}\n\nconst STROKE_KEYS = [\n\t'stroke',\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] as const;\n\n/**\n * Style of a Pathfinder or Shape Builder result taken from `node`: the node's own style for a\n * filled area, the stroke's paint as a fill for a stroke's area, or (for Outline edges) a stroke\n * in the paint of the area the edge bounds. Never carries the node's id, transform or geometry.\n */\nexport function vectorResultStyle(node: VectorPathNode, paint: VectorPathfinderPaint, edge = false): Omit<VectorPathNode, 'id' | 'subpaths'> {\n\tconst { id: _id, subpaths: _s, transform: _t, effects: _e, booleanOperation: _b, ...rest } = node;\n\tconst base: Omit<VectorPathNode, 'id' | 'subpaths'> = { ...rest, fillRule: 'nonzero' };\n\tif (edge) {\n\t\tfor (const k of STROKE_KEYS) delete (base as Record<string, unknown>)[k];\n\t\tconst stroke = paint === 'stroke' || !hasPaint(node.fill) ? node.stroke : node.fill;\n\t\treturn { ...base, fill: null, stroke: stroke ?? '#000000', strokeWidth: node.strokeWidth ?? 1 };\n\t}\n\tif (paint === 'stroke') {\n\t\tfor (const k of STROKE_KEYS) delete (base as Record<string, unknown>)[k];\n\t\treturn { ...base, fill: node.stroke ?? null, stroke: null };\n\t}\n\treturn base;\n}\n\n// ---------------------------------------------------------------------------\n// Shape Builder\n// ---------------------------------------------------------------------------\n\n/**\n * A run of a path between the points where other paths meet it (or it ends): a piece of an\n * unfilled open path, or an edge of another path's outline.\n */\nexport interface VectorBuilderLinePiece {\n\tpath: BezierPath;\n\tsegments: BezierSegment[];\n\t/** A loop with no junction on it (the whole subpath). */\n\tclosed: boolean;\n\t/** Index of the entry it belongs to (the front-most where outlines coincide). */\n\tsource: number;\n\tbounds: { minX: number; minY: number; maxX: number; maxY: number };\n}\n\n/** The Shape Builder's planar map of a set of paths. */\nexport interface VectorShapeBuilderMap {\n\tentries: VectorPathEntry[];\n\tmap: PlanarMap;\n\t/** Entries with no closed subpath (cutting lines; their pieces can be erased). */\n\tlines: boolean[];\n\tpieces: VectorBuilderLinePiece[];\n\t/** The edges of the other entries' outlines, cut where paths meet (built on first use: erasing). */\n\treadonly edges: VectorBuilderLinePiece[];\n\t/** How far an edge may lie from the outline it came from (document units). */\n\ttolerance: number;\n}\n\n/** What a Shape Builder gesture touches at a point. */\nexport type VectorBuilderHit = { face: number } | { line: number } | { edge: number };\n\n/** The faces, line pieces and edges a gesture touched, in the order touched. */\nexport interface VectorBuilderTouched {\n\tfaces: number[];\n\tlines: number[];\n\tedges: number[];\n}\n\n/**\n * The Shape Builder map of `entries` (bottom to top). Filled open subpaths that enclose an area\n * are closed. Null when nothing can be mapped.\n */\nexport function vectorShapeBuilderMap(entries: readonly VectorPathEntry[]): VectorShapeBuilderMap | null {\n\tconst lines: boolean[] = [];\n\tconst inputs = entries.map((entry) => {\n\t\tconst filled = hasPaint(entry.node.fill);\n\t\tconst subpaths = entry.node.subpaths.filter(usable).map((sp) => (!sp.closed && filled && subpathEnclosesArea(sp) ? { ...sp, closed: true } : sp));\n\t\tlines.push(!subpaths.some((s) => s.closed));\n\t\treturn { path: worldOf(entry, subpaths), fillRule: entry.node.fillRule ?? 'nonzero' };\n\t});\n\tconst map = PlanarMap.build(inputs);\n\tif (!map) return null;\n\tlet chains: PlanarChain[] | null = null;\n\tconst runs = (line: boolean) =>\n\t\t(chains ??= map.chains())\n\t\t\t.filter((c) => lines[c.source] === line && !(line && c.closed))\n\t\t\t.map((c) => {\n\t\t\t\tconst path = contoursToPath([{ segments: c.segments, closed: c.closed }]);\n\t\t\t\treturn { path, segments: c.segments, closed: c.closed, source: c.source, bounds: bezierPathBounds(path)! };\n\t\t\t});\n\tconst pieces = lines.some(Boolean) ? runs(true) : [];\n\tlet edges: VectorBuilderLinePiece[] | null = null;\n\tlet size = 0;\n\tfor (const input of inputs) {\n\t\tconst b = bezierPathBounds(input.path);\n\t\tif (b) size = Math.max(size, b.maxX - b.minX, b.maxY - b.minY);\n\t}\n\treturn {\n\t\tentries: [...entries],\n\t\tmap,\n\t\tlines,\n\t\tpieces,\n\t\tget edges() {\n\t\t\treturn (edges ??= runs(false));\n\t\t},\n\t\ttolerance: 0.2 * Math.max(size / 2000, 0.25)\n\t};\n}\n\n/** The run of `pieces` nearest `p` within `tol`, with its distance. */\nfunction pieceNear(pieces: readonly VectorBuilderLinePiece[], p: Vec2, tol: number): { index: number; d: number } | null {\n\tconst point: Point = [p.x, p.y];\n\tlet best: { index: number; d: number } | null = null;\n\tpieces.forEach((piece, index) => {\n\t\tconst b = piece.bounds;\n\t\tif (p.x < b.minX - tol || p.x > b.maxX + tol || p.y < b.minY - tol || p.y > b.maxY + tol) return;\n\t\tconst d = distanceToBezierPath(piece.path, point);\n\t\tif (d <= tol && (!best || d < best.d)) best = { index, d };\n\t});\n\treturn best;\n}\n\n/**\n * The face under `p`, or with `lineTolerance` (erasing) first the nearer of the line piece and\n * the outline edge that close.\n */\nexport function vectorShapeBuilderHit(m: VectorShapeBuilderMap, p: Vec2, lineTolerance: number | null = null): VectorBuilderHit | null {\n\tif (lineTolerance !== null && lineTolerance > 0) {\n\t\tconst line = pieceNear(m.pieces, p, lineTolerance);\n\t\tconst edge = pieceNear(m.edges, p, lineTolerance);\n\t\tif (edge && (!line || edge.d < line.d)) return { edge: edge.index };\n\t\tif (line) return { line: line.index };\n\t}\n\tconst face = m.map.faceAt([p.x, p.y]);\n\treturn face >= 0 ? { face } : null;\n}\n\n/** Points along a polyline no more than `step` apart (the pointer's path through faces). */\nexport function samplePolyline(points: readonly Vec2[], step: number): Vec2[] {\n\tconst out: Vec2[] = [];\n\tconst s = Math.max(step, 1e-3);\n\tpoints.forEach((p, i) => {\n\t\tif (i > 0) {\n\t\t\tconst a = points[i - 1];\n\t\t\tconst n = Math.min(4000, Math.ceil(Math.hypot(p.x - a.x, p.y - a.y) / s));\n\t\t\tfor (let k = 1; k < n; k++) out.push({ x: a.x + ((p.x - a.x) * k) / n, y: a.y + ((p.y - a.y) * k) / n });\n\t\t}\n\t\tout.push(p);\n\t});\n\treturn out;\n}\n\n/** Adds what a point hits to `touched` (each face or piece once). */\nexport function addBuilderHit(touched: VectorBuilderTouched, hit: VectorBuilderHit | null): boolean {\n\tif (!hit) return false;\n\tconst [list, i] = 'face' in hit ? [touched.faces, hit.face] : 'line' in hit ? [touched.lines, hit.line] : [touched.edges, hit.edge];\n\tif (list.includes(i)) return false;\n\tlist.push(i);\n\treturn true;\n}\n\n/** The faces just either side of the middle of a line piece (-1 outside every face). */\nfunction sides(m: VectorShapeBuilderMap, piece: VectorBuilderLinePiece): [number, number] {\n\tconst seg = piece.segments[Math.floor(piece.segments.length / 2)];\n\tconst mid = segmentPoint(seg, 0.5);\n\tconst t = segmentTangent(seg, 0.5);\n\tconst chord = Math.hypot(seg[seg.length - 1][0] - seg[0][0], seg[seg.length - 1][1] - seg[0][1]);\n\tconst off = Math.max(1e-6, Math.min(0.05, 0.05 * chord));\n\treturn [m.map.faceAt([mid[0] - t[1] * off, mid[1] + t[0] * off]), m.map.faceAt([mid[0] + t[1] * off, mid[1] - t[0] * off])];\n}\n\n/**\n * The edges a gesture deletes: those it touched that bound none of the faces it touched (an edge\n * of a deleted or merged face goes with the face, so an erase drag that crosses an edge into a\n * face deletes the face).\n */\nexport function vectorBuilderErasedEdges(m: VectorShapeBuilderMap, touched: VectorBuilderTouched): number[] {\n\tif (!touched.edges.length) return [];\n\tconst faces = new Set(touched.faces);\n\treturn touched.edges.filter((k) => m.edges[k] && !sides(m, m.edges[k]).some((f) => f >= 0 && faces.has(f)));\n}\n\n/**\n * What becomes of one entry: unchanged (absent), removed (`null`), or new outlines (document\n * space). `fill`: what is left of its area, filled non-zero (subpaths an erased edge opened stay\n * open); `outline`: its own outline with erased edges cut out, its fill rule kept; `pieces`: the\n * pieces of a cutting line that stay, each its own path.\n */\nexport type VectorBuilderReplacement = { kind: 'fill'; path: BezierPath } | { kind: 'outline'; path: BezierPath } | { kind: 'pieces'; paths: BezierPath[] } | null;\n\nexport interface VectorShapeBuilderPlan {\n\treplace: Map<number, VectorBuilderReplacement>;\n\t/** The merged shape (document space), styled like entry `style`; `currentFill` when only outlines enclosed it. */\n\tmerged: { path: BezierPath; style: number; currentFill: boolean } | null;\n}\n\n/**\n * What a gesture does: merging (or with `erase`, deleting) the touched faces takes them away\n * from every shape covering them; line pieces touched (erasing) or lying between touched faces\n * go; erased edges (`vectorBuilderErasedEdges`) are cut out of every outline they lie on;\n * merging adds the union of the faces as a new shape styled like the top-most shape over the\n * first face touched (or, for a face only outlines enclose, the top-most shape with the current\n * fill).\n */\nexport function planShapeBuilder(m: VectorShapeBuilderMap, touched: VectorBuilderTouched, erase: boolean): VectorShapeBuilderPlan {\n\tconst replace = new Map<number, VectorBuilderReplacement>();\n\tconst picked = new Set(touched.faces);\n\tconst faces = m.map.faces;\n\tconst sourcesOf = faces.map((_, i) => m.map.faceSources(i));\n\tconst cuts = erase ? vectorBuilderErasedEdges(m, touched).map((k) => m.edges[k]) : [];\n\tm.entries.forEach((entry, i) => {\n\t\tif (m.lines[i]) {\n\t\t\tconst own = m.pieces.flatMap((piece, k) => (piece.source === i ? [k] : []));\n\t\t\tconst keep = own.filter((k) => !touched.lines.includes(k) && !sides(m, m.pieces[k]).every((f) => f >= 0 && picked.has(f)));\n\t\t\tif (keep.length === own.length) return;\n\t\t\treplace.set(i, keep.length ? { kind: 'pieces', paths: keep.map((k) => m.pieces[k].path) } : null);\n\t\t\treturn;\n\t\t}\n\t\tconst inUnion = touched.faces.some((f) => sourcesOf[f].includes(i));\n\t\tif (!inUnion && !cuts.length) return;\n\t\tlet rest: BezierPath;\n\t\tif (inUnion) {\n\t\t\tconst kept = faces.flatMap((_, f) => (sourcesOf[f].includes(i) && !picked.has(f) ? [f] : []));\n\t\t\trest = kept.length ? m.map.merge(kept) : [];\n\t\t} else rest = worldOf(entry, entry.node.subpaths.filter(usable));\n\t\t// Each erased edge comes off every outline it lies on (shared edges from each shape).\n\t\tconst fillCloses = hasPaint(entry.node.fill);\n\t\tlet cut = false;\n\t\tfor (const piece of cuts) {\n\t\t\tconst r = rest.length ? cutOutPiece(rest, piece, fillCloses, m.tolerance) : null;\n\t\t\tif (r) {\n\t\t\t\trest = r;\n\t\t\t\tcut = true;\n\t\t\t}\n\t\t}\n\t\tif (!inUnion && !cut) return;\n\t\treplace.set(i, rest.length ? { kind: inUnion ? 'fill' : 'outline', path: rest } : null);\n\t});\n\tlet merged: VectorShapeBuilderPlan['merged'] = null;\n\tif (!erase && touched.faces.length) {\n\t\tconst path = m.map.merge(touched.faces);\n\t\tconst covered = touched.faces.map((f) => sourcesOf[f]).find((s) => s.length);\n\t\tconst lastShape = m.lines.lastIndexOf(false);\n\t\tconst style = covered ? covered[covered.length - 1] : lastShape >= 0 ? lastShape : m.entries.length - 1;\n\t\tif (path.length) merged = { path, style, currentFill: !covered };\n\t}\n\treturn { replace, merged };\n}\n\n// ---------------------------------------------------------------------------\n// Fill regions\n// ---------------------------------------------------------------------------\n\n/**\n * The planar map of every visible path's outline as drawn (locked ones included), for the paint\n * bucket. `gap` closes gaps up to that distance between loose ends.\n */\nexport function vectorFillMap(doc: VectorDocument, gap = 0): PlanarMap | null {\n\tconst entries = vectorPathEntries(doc, null, { locked: true });\n\treturn PlanarMap.build(\n\t\tentries.map((e) => ({ path: worldOf(e, e.node.subpaths.filter(usable)), fills: false })),\n\t\t{ gap, maxSegments: 20000 }\n\t);\n}\n","import { HistoryManager } from '../history/HistoryManager.js';\nimport type { Command } from '../history/types.js';\nimport { fitCubics } from '../canvas/curveFit.js';\nimport { pathBoolean } from '../canvas/pathops/boolean.js';\nimport { pathfinder as runPathfinder } from '../canvas/pathops/pathfinder.js';\nimport type { PlanarMap } from '../canvas/pathops/planar.js';\nimport { bezierPathBounds, bezierPathToSvg, transformBezierPath, type BezierPath } from '../canvas/pathops/bezier.js';\nimport type { Point } from '../canvas/types.js';\nimport {\n\tbezierToSubpaths,\n\tboundsFromPoints,\n\tboundsIntersect,\n\tcloneVectorDocument,\n\tcloneVectorNode,\n\tcollectPathNodes,\n\tconvertAnchor,\n\tellipseSubpath,\n\tfindVectorNode,\n\thitTestVectorNode,\n\thitTestVectorNodes,\n\tinsertAnchor,\n\tlineSubpath,\n\tlocateVectorNode,\n\tmoveAnchorHandle,\n\tnearestSegment,\n\thasPaint,\n\tnodeMatrix,\n\tinvertMatrix,\n\tapplyMatrix,\n\tapplyMatrixLinear,\n\tmultiplyMatrices,\n\tmatrixRotation,\n\tvectorWorldMatrix,\n\tvectorParentMatrix,\n\tvectorPathWorldBezier,\n\tIDENTITY_MATRIX,\n\tpolygonSubpath,\n\trectSubpath,\n\tremoveAnchors,\n\treverseSubpath,\n\trotateMatrix,\n\tscaleMatrix,\n\tstarSubpath,\n\tsubpathsToBezier,\n\ttransformVectorNode,\n\ttranslateAnchor,\n\ttranslateMatrix,\n\tunionBounds,\n\tvectorNodeBounds,\n\twalkVectorNodes,\n\ttype AffineMatrix\n} from './geometry.js';\nimport {\n\tcornerBisectorPoint,\n\tcornerRadiusChange,\n\tfitCornerRadius,\n\tnextVectorCornerKind,\n\tsetAnchorCorner,\n\tsetSubpathCorners,\n\tvectorCorners,\n\ttype VectorCornerInfo\n} from './corners.js';\nimport { strokeWidthProfileOf, vectorStrokeOutlinePath, vectorStrokeParts } from './stroke.js';\nimport { nearestStrokePosition, strokePositionPoint } from './varstroke.js';\nimport {\n\tgenerateWidthProfile,\n\tinsertWidthPoint,\n\tpressureWidthProfile,\n\tremoveWidthPoints,\n\treverseWidthProfile,\n\tsanitizeWidthProfile,\n\tsetWidthPoint,\n\twidthProfileAt,\n\twidthProfileMaxScale,\n\twidthProfilePreset,\n\ttype VectorTaperOptions,\n\ttype VectorWaveOptions,\n\ttype VectorWidthPreset,\n\tVECTOR_WIDTH_PROFILE_MAX_POINTS,\n\tVECTOR_WIDTH_SAME_T\n} from './width.js';\nimport { parseVectorSvg, vectorDocumentToSvg, vectorPathLocalBounds, type VectorSvgExportOptions } from './svg.js';\nimport {\n\tapplyVectorGradientPatch,\n\tdefaultVectorGradient,\n\tinsertVectorGradientStop,\n\tisVectorGradient,\n\tmoveVectorGradientHandle,\n\tmoveVectorGradientStop,\n\tremoveVectorGradientStop,\n\tsetVectorGradientVector,\n\tsortedVectorGradientStops,\n\tvectorGradientMidpointFromPosition,\n\ttype VectorGradientHandle,\n\ttype VectorGradientPatch\n} from './gradient.js';\nimport {\n\tgetVectorGradientAnnotator,\n\thitVectorGradientAnnotator,\n\tvectorGradientBarPosition,\n\tVECTOR_GRADIENT_OFF_BAR,\n\tVECTOR_GRADIENT_STOP_GAP,\n\ttype VectorGradientAnnotator,\n\ttype VectorGradientPart,\n\ttype VectorGradientTarget\n} from './gradientTool.js';\nimport {\n\texpandVectorBlend,\n\tisVectorBlendGroup,\n\trefreshVectorBlends,\n\treleaseVectorBlend,\n\tvectorBlendKeyTargets\n} from './blendGroup.js';\nimport {\n\tcreateVectorEffect,\n\texpandVectorAppearance,\n\thasOwnVectorAppearance,\n\thasVectorAppearance,\n\tisVectorLiveBoolean,\n\tnormalizeVectorEffectParams,\n\tvectorAppearanceOutline,\n\tvectorEnteredGroups,\n\tVECTOR_EFFECT_LABELS\n} from './appearance.js';\nimport { constrainToLeave, VECTOR_CONSTRUCTION_ANGLES, VectorSnapTargets, type VectorSnapGuides, type VectorSnapLeave } from './snap.js';\nimport {\n\tbendSegment as bendSubpathSegment,\n\tcurvatureMoveAnchor,\n\tcutSubpathAt,\n\tjoinSubpaths,\n\tremoveAnchorsKeepingShape,\n\tremoveRedundantPoints as removeRedundantSubpathPoints,\n\tsmoothSubpath,\n\tsplitSubpathAt\n} from './pathEdit.js';\nimport { eraseFromFill, eraseFromOutline, eraserRegion, knifePath, polylineBounds, simplifyPolyline } from './pathCut.js';\nimport { LazyMouse, recognizeInkShape, vectorInkShapeName, type VectorInkShape } from './ink.js';\nimport {\n\taddBuilderHit,\n\tplanShapeBuilder,\n\tsamplePolyline,\n\tvectorFillMap,\n\tvectorPathEntries,\n\tvectorPathfinderShape,\n\tvectorResultStyle,\n\tvectorBuilderErasedEdges,\n\tvectorShapeBuilderHit,\n\tvectorShapeBuilderMap,\n\ttype VectorBuilderTouched,\n\ttype VectorShapeBuilderMap\n} from './builder.js';\nimport type { VectorGradient } from './types.js';\nimport type {\n\tVec2,\n\tVectorEffect,\n\tVectorEffectType,\n\tVectorAlign,\n\tVectorAnchor,\n\tVectorAnchorRef,\n\tVectorAnchorType,\n\tVectorArrange,\n\tVectorAverageAxis,\n\tVectorBlend,\n\tVectorBlendOptions,\n\tVectorBooleanOperation,\n\tVectorBounds,\n\tVectorCornerKind,\n\tVectorDefaults,\n\tVectorDistribute,\n\tVectorDocument,\n\tVectorGroupNode,\n\tVectorHandleRef,\n\tVectorModifiers,\n\tVectorNode,\n\tVectorPathfinderOperation,\n\tVectorPathNode,\n\tVectorSegmentRef,\n\tVectorStyle,\n\tVectorSubpath,\n\tVectorTool,\n\tVectorTransformHandle,\n\tVectorViewport,\n\tVectorWidthPoint\n} from './types.js';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface VectorSnapOptions {\n\t/** Grid size in document units (0 = off, default 0). Points and boxes that snap to no object snap to it. */\n\tgrid: number;\n\t/**\n\t * Snap to other objects (default true): anchors, paths and their intersections, bounding-box\n\t * edges and centres, the artboard, construction angles, equal spacing and matching sizes.\n\t */\n\tobjects: boolean;\n\t/** Show smart guide lines and labels while snapping (default true). */\n\tguides: boolean;\n\t/** How near a target pulls, in screen pixels (default 5). */\n\tthreshold: number;\n\t/** Construction guide angles through the point a drawn point leaves from, in degrees (default 0, 45, 90, 135). */\n\tangles: readonly number[];\n}\n\n/** Snap options new editors start with. Hold Cmd / Ctrl (`mod`) during a drag to bypass snapping. */\nexport const DEFAULT_VECTOR_SNAP: Readonly<VectorSnapOptions> = { grid: 0, objects: true, guides: true, threshold: 5, angles: VECTOR_CONSTRUCTION_ANGLES };\n\n/** How path-editing gestures and the pencil, knife and eraser behave. */\nexport interface VectorPathEditOptions {\n\t/**\n\t * Deleting anchors refits the neighbouring handles so the shape stays (default true). Off,\n\t * the neighbours are joined with their handles as they are.\n\t */\n\tkeepShapeOnDelete: boolean;\n\t/**\n\t * Dragging one anchor with the direct tool bends its two segments like the Curvature tool,\n\t * keeping the segments beyond still (default false). Alt held during the drag flips it.\n\t */\n\tcurvatureMoves: boolean;\n\t/** Pencil lazy-mouse smoothing, 0–1: the pulled string is up to 100 screen pixels long (default 0, off). */\n\tpencilSmoothing: number;\n\t/** Pencil strokes that look like a line, rectangle, ellipse or polygon become that shape (default false). */\n\trecognizeShapes: boolean;\n\t/** Eraser diameter in document units (default 10). */\n\teraserSize: number;\n\t/**\n\t * Record pointer pressure on pencil strokes as a width profile: `pen` (default) for pen input\n\t * only, `all` for any pointer that reports pressure, `off` never.\n\t */\n\tpencilPressure: 'pen' | 'all' | 'off';\n\t/**\n\t * Paint bucket: gaps up to this distance (document units) between loose ends of outlines are\n\t * closed when finding the region to fill (default 0, off).\n\t */\n\tfillGap: number;\n}\n\n/** The gap the toolbars' \"Close gaps\" toggle sets (document units). */\nexport const VECTOR_FILL_GAP = 6;\n\n/** Path-editing options new editors start with. */\nexport const DEFAULT_VECTOR_PATH_EDIT: Readonly<VectorPathEditOptions> = {\n\tkeepShapeOnDelete: true,\n\tcurvatureMoves: false,\n\tpencilSmoothing: 0,\n\trecognizeShapes: false,\n\teraserSize: 10,\n\tpencilPressure: 'pen',\n\tfillGap: 0\n};\n\n/** Longest pencil lazy-mouse string (at smoothing 1), in screen pixels. */\nexport const VECTOR_PENCIL_STRING_PX = 100;\n\nexport interface VectorEditorOptions {\n\tdocument?: VectorDocument;\n\t/** History to record into, e.g. one shared with the rest of the app (default: a new one). */\n\thistory?: HistoryManager;\n\t/** Starting tool (default `select`). */\n\ttool?: VectorTool;\n\t/** Style and settings for new shapes and paths. */\n\tdefaults?: VectorDefaults;\n\tviewport?: Partial<VectorViewport>;\n\tsnap?: Partial<VectorSnapOptions>;\n\tediting?: Partial<VectorPathEditOptions>;\n\t/** Pick distance for anchors, handles and outlines in screen pixels (default 6). */\n\thitRadius?: number;\n\t/** Id factory for new nodes. */\n\tcreateId?: () => string;\n\t/** Called after the document changes (edits, undo, redo, load). */\n\tonChange?: (document: VectorDocument) => void;\n}\n\n/** A pointer event as the controller needs it. Positions are in screen pixels relative to the canvas. */\nexport interface VectorPointerInput extends VectorModifiers {\n\t/** 0 = primary, 1 = middle (pans), 2 = secondary. Default 0. */\n\tbutton?: number;\n\t/** `PointerEvent.pressure` (0–1), recorded on pencil strokes. */\n\tpressure?: number;\n\t/** `PointerEvent.pointerType` (`mouse`, `pen`, `touch`). */\n\tpointerType?: string;\n}\n\n/** One width point of a path's stroke profile; `subpath` is the subpath it is shown on. */\nexport interface VectorWidthPointRef {\n\tnodeId: string;\n\tindex: number;\n\tsubpath: number;\n}\n\n/** A spot on a stroke (width tool): where a drag would add a width point. Positions in document units. */\nexport interface VectorStrokeSpot {\n\tnodeId: string;\n\tsubpath: number;\n\t/** Position along the subpath by length, 0–1. */\n\tt: number;\n\tpoint: Vec2;\n\t/** Ends of the stroke's width across the path there. */\n\tleft: Vec2;\n\tright: Vec2;\n}\n\n/**\n * The width point being edited numerically (double-click with the width tool): `index` is the\n * point's, or null for a new point at `t` (a double-click on a stroke away from its points).\n * `left` / `right` are side widths in document units (how far the stroke reaches on each side),\n * `point` is where it sits on the path, in document space.\n */\nexport interface VectorWidthPointEdit {\n\tnodeId: string;\n\tsubpath: number;\n\tindex: number | null;\n\tt: number;\n\tleft: number;\n\tright: number;\n\tpoint: Vec2;\n}\n\n/** A key press as the controller needs it. */\nexport interface VectorKeyInput extends VectorModifiers {\n\t/** `KeyboardEvent.key`. */\n\tkey: string;\n}\n\n/** What the pointer is over. */\nexport interface VectorHover {\n\tnodeId: string | null;\n\tanchor?: VectorAnchorRef;\n\thandle?: VectorHandleRef;\n\t/** A segment under the pointer (direct tool and scissors); `point` is in document space. */\n\tsegment?: { nodeId: string; subpath: number; segment: number; t: number; point: Vec2 };\n\ttransformHandle?: VectorTransformHandle;\n\t/** A live corner widget (direct tool): the corner's anchor. */\n\tcorner?: VectorAnchorRef;\n\t/** A width point (width tool): its centre or the end of its left / right side. */\n\twidthPoint?: VectorWidthPointRef & { part: 'center' | 'left' | 'right' };\n\t/** A spot on a stroke away from its width points (width tool). */\n\tstrokeSpot?: VectorStrokeSpot;\n\t/** A part of the gradient annotator (gradient tool). */\n\tgradient?: VectorGradientPart;\n}\n\n/** The drag in progress, for overlays and status text. */\nexport type VectorInteraction =\n\t| { type: 'marquee'; bounds: VectorBounds }\n\t| { type: 'move'; dx: number; dy: number }\n\t| { type: 'scale'; handle: VectorTransformHandle; sx: number; sy: number }\n\t| { type: 'rotate'; angle: number }\n\t| { type: 'anchors'; dx: number; dy: number }\n\t| { type: 'handle'; ref: VectorHandleRef }\n\t| { type: 'bend'; ref: VectorSegmentRef }\n\t| { type: 'corner'; ref: VectorAnchorRef; radius: number }\n\t| { type: 'draw'; tool: VectorTool; nodeId: string; bounds: VectorBounds; string?: [Vec2, Vec2] }\n\t| { type: 'stroke'; tool: 'knife' | 'eraser'; points: Vec2[]; width: number }\n\t| { type: 'builder'; mode: 'merge' | 'erase'; points: Vec2[] }\n\t| { type: 'width'; ref: VectorWidthPointRef; mode: 'width' | 'move' | 'copy'; width: number }\n\t| { type: 'pen'; nodeId: string }\n\t/** A gradient tool drag: drawing a new vector, or a handle, stop, midpoint or the bar (`removing`: a stop dragged off the bar). */\n\t| { type: 'gradient'; part: 'create' | VectorGradientPart['kind']; handle?: VectorGradientHandle; removing?: boolean }\n\t| { type: 'pan' };\n\n/** A snapshot of the editor. The same object is returned until something changes. */\nexport interface VectorEditorState {\n\tdocument: VectorDocument;\n\ttool: VectorTool;\n\t/** Selected node ids (top-level nodes or group members picked with the direct tool). */\n\tselection: readonly string[];\n\t/** Selected anchors (direct tool). */\n\tselectedAnchors: readonly VectorAnchorRef[];\n\t/** The selected width points (width tool, all on one path); Delete removes them. */\n\tselectedWidthPoints: readonly VectorWidthPointRef[];\n\t/** The width point open for numeric editing (double-click with the width tool), if any. */\n\twidthPointEdit: VectorWidthPointEdit | null;\n\t/** The path whose anchors are being edited, if any. */\n\teditingId: string | null;\n\t/** Axis-aligned bounds of the selected nodes in document units. */\n\tselectionBounds: VectorBounds | null;\n\t/** The selection box, rotated with the selection when its nodes share a rotation. */\n\tselectionFrame: VectorSelectionFrame | null;\n\tviewport: VectorViewport;\n\tdefaults: Readonly<Required<Pick<VectorDefaults, 'polygonSides' | 'starPoints' | 'starInnerRatio' | 'cornerRadius'>> & Pick<VectorDefaults, 'cornerKind'> & VectorStyle>;\n\tsnap: VectorSnapOptions;\n\tediting: VectorPathEditOptions;\n\t/** Smart guides of the snap in progress, in document units (null when there are none). */\n\tguides: VectorSnapGuides | null;\n\thitRadius: number;\n\thover: VectorHover | null;\n\t/** A CSS cursor for the canvas. */\n\tcursor: string;\n\tinteraction: VectorInteraction | null;\n\t/** The path the pen tool is adding to. */\n\tpen: { nodeId: string; subpath: number } | null;\n\t/** Last pointer position in document units (for the pen preview), or null when outside. */\n\tpointer: Vec2 | null;\n\t/** Shape Builder and paint bucket highlight: faces, line pieces and the gesture's trail. */\n\tbuilder: VectorBuilderPreview | null;\n\t/** The Blend tool's first click: the shape (and anchor of its first subpath) the next click blends from. */\n\tblendStart: VectorBlendStart | null;\n\t/** Options new blends get. */\n\tblendDefaults: VectorBlendOptions;\n\t/** The paint the gradient tool and gradient editor work on, and the selected stop (index into the sorted stops). */\n\tgradientEdit: { target: VectorGradientTarget; stop: number | null };\n\tcanUndo: boolean;\n\tcanRedo: boolean;\n\tundoLabel: string;\n\tredoLabel: string;\n\t/** Bumped on every change. */\n\trevision: number;\n}\n\nexport type VectorActionResult = { ok: true } | { ok: false; message: string };\n\n/** The shape the Blend tool blends from (its first click). */\nexport interface VectorBlendStart {\n\tnodeId: string;\n\t/** The clicked anchor of its first subpath, or null for the shape as a whole. */\n\tanchor: number | null;\n}\n\n/** Blend settings and options of `VectorEditorController.blend`. */\nexport interface VectorBlendInput extends VectorBlendOptions {\n\t/** Nodes to blend (default: the selection), in stacking order unless `keepOrder`. */\n\tids?: readonly string[];\n\t/** Blend in the order given instead of stacking order. */\n\tkeepOrder?: boolean;\n\t/** Start anchor of each key's first subpath, by key id. */\n\tstarts?: Record<string, number>;\n\tname?: string;\n}\n\nconst BLEND_OPTION_KEYS = ['spacing', 'steps', 'distance', 'orientToSpine', 'linear'] as const;\n\n/** The blend options in `input`, valid ones only. */\nfunction blendOptionsOf(input: Partial<VectorBlendOptions>): VectorBlendOptions {\n\tconst out: VectorBlendOptions = {};\n\tif (input.spacing === 'steps' || input.spacing === 'distance' || input.spacing === 'smoothColor') out.spacing = input.spacing;\n\tif (typeof input.steps === 'number' && Number.isFinite(input.steps)) out.steps = Math.min(1000, Math.max(1, Math.round(input.steps)));\n\tif (typeof input.distance === 'number' && Number.isFinite(input.distance) && input.distance > 0) out.distance = input.distance;\n\tif (typeof input.orientToSpine === 'boolean') out.orientToSpine = input.orientToSpine;\n\tif (typeof input.linear === 'boolean') out.linear = input.linear;\n\treturn out;\n}\n\n/**\n * What the Shape Builder or paint bucket highlights, in document units: the faces (filled paths\n * as SVG path data) under the pointer or touched by the drag, the line pieces an erase would\n * delete, and the drag's trail. `mode` is `merge`, `erase` (Alt) or `fill` (paint bucket).\n */\nexport interface VectorBuilderPreview {\n\tmode: 'merge' | 'erase' | 'fill';\n\tfaces: BezierPath[];\n\tlines: BezierPath[];\n\ttrail: Vec2[] | null;\n}\n\n/** Result of a Shape Builder gesture. */\nexport interface VectorShapeBuilderResult {\n\tok: boolean;\n\tmessage?: string;\n\t/** Id of the merged shape (none when erasing). */\n\tmerged: string | null;\n\tfaces: number;\n\tlines: number;\n\t/** Outline edges erased (cut out of the paths they lie on). */\n\tedges: number;\n}\n\n/** A face of the Shape Builder map (document units). */\nexport interface VectorShapeBuilderFace {\n\tindex: number;\n\t/** SVG path data. */\n\td: string;\n\tarea: number;\n\t/** Ids of the paths whose fill covers it, bottom to top (empty for an area only outlines enclose). */\n\tsources: string[];\n\t/** A point inside it. */\n\tpoint: Vec2 | null;\n}\n\nexport interface AddVectorNodeOptions {\n\t/** Group to add into (default: the root). */\n\tparentId?: string;\n\t/** Position in the parent's list (default: on top). */\n\tindex?: number;\n\t/** Select the new node (default true). */\n\tselect?: boolean;\n\t/** Undo step label. */\n\tlabel?: string;\n}\n\n/** A shape description for `addShape` (and the agent command of the same name). */\nexport interface VectorShapeInput extends VectorStyle {\n\tkind: 'rect' | 'ellipse' | 'polygon' | 'star' | 'line';\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\t/** Live corner radius of a rectangle, polygon or star (default: the editor default). */\n\tcornerRadius?: number;\n\tcornerKind?: VectorCornerKind;\n\tsides?: number;\n\tpoints?: number;\n\tinnerRatio?: number;\n\tname?: string;\n}\n\n/** Every field of `VectorStyle`, in the order style panels and commands list them. */\nexport const VECTOR_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 satisfies readonly (keyof VectorStyle)[];\n\n/** Clipboard payload type written by `copy()`. */\nexport const VECTOR_CLIPBOARD_TYPE = 'pixerate/vector';\n\nconst DEFAULT_DEFAULTS = {\n\tfill: '#d4d4d8',\n\tstroke: '#18181b',\n\tstrokeWidth: 2,\n\tpolygonSides: 6,\n\tstarPoints: 5,\n\tstarInnerRatio: 0.5,\n\tcornerRadius: 0\n};\n\nconst MIN_ZOOM = 0.05;\nconst MAX_ZOOM = 64;\n/** Screen distance of the rotation handle above the selection box. */\nexport const VECTOR_ROTATE_HANDLE_OFFSET = 24;\n\n// ---------------------------------------------------------------------------\n// Helpers shared with the render module\n// ---------------------------------------------------------------------------\n\nconst sameRef = (a: VectorAnchorRef, b: VectorAnchorRef) => a.nodeId === b.nodeId && a.subpath === b.subpath && a.index === b.index;\n\n/** Ids of the path nodes whose anchors are shown (the edited path plus selected paths). */\nexport function getEditablePathIds(state: Pick<VectorEditorState, 'document' | 'selection' | 'editingId' | 'tool' | 'pen'>): string[] {\n\tif (state.tool !== 'direct' && state.tool !== 'pen') return [];\n\tconst ids = new Set<string>();\n\tif (state.pen) ids.add(state.pen.nodeId);\n\tif (state.tool === 'direct') {\n\t\tif (state.editingId) ids.add(state.editingId);\n\t\tfor (const id of state.selection) {\n\t\t\tconst node = findVectorNode(state.document.nodes, id);\n\t\t\tif (node && !node.locked) for (const p of collectPathNodes(node)) if (!p.locked) ids.add(p.id);\n\t\t}\n\t}\n\treturn [...ids].filter((id) => findVectorNode(state.document.nodes, id)?.type === 'path');\n}\n\n/** Every anchor drawn on the canvas. */\nexport function getVisibleAnchors(state: Pick<VectorEditorState, 'document' | 'selection' | 'editingId' | 'tool' | 'pen'>): VectorAnchorRef[] {\n\tconst out: VectorAnchorRef[] = [];\n\tfor (const id of getEditablePathIds(state)) {\n\t\tconst node = findVectorNode(state.document.nodes, id) as VectorPathNode;\n\t\tnode.subpaths.forEach((sp, s) => sp.anchors.forEach((_, i) => out.push({ nodeId: id, subpath: s, index: i })));\n\t}\n\treturn out;\n}\n\n/**\n * Handles drawn on the canvas: those of selected anchors and of the segments next to them\n * (direct tool), or of the last anchor of the pen path.\n */\nexport function getVisibleHandles(\n\tstate: Pick<VectorEditorState, 'document' | 'selection' | 'editingId' | 'tool' | 'pen' | 'selectedAnchors'>\n): VectorHandleRef[] {\n\tconst out: VectorHandleRef[] = [];\n\tconst seen = new Set<string>();\n\tconst push = (node: VectorPathNode, s: number, i: number, handle: 'in' | 'out') => {\n\t\tconst sp = node.subpaths[s];\n\t\tconst a = sp?.anchors[i];\n\t\tif (!a || !a[handle]) return;\n\t\tconst key = `${node.id}:${s}:${i}:${handle}`;\n\t\tif (seen.has(key)) return;\n\t\tseen.add(key);\n\t\tout.push({ nodeId: node.id, subpath: s, index: i, handle });\n\t};\n\tif (state.tool === 'pen' && state.pen) {\n\t\tconst node = findVectorNode(state.document.nodes, state.pen.nodeId);\n\t\tconst sp = node?.type === 'path' ? node.subpaths[state.pen.subpath] : undefined;\n\t\tif (node?.type === 'path' && sp) {\n\t\t\tconst last = sp.anchors.length - 1;\n\t\t\tpush(node, state.pen.subpath, last, 'in');\n\t\t\tpush(node, state.pen.subpath, last, 'out');\n\t\t}\n\t\treturn out;\n\t}\n\tif (state.tool !== 'direct') return out;\n\tconst editable = new Set(getEditablePathIds(state));\n\tfor (const ref of state.selectedAnchors) {\n\t\tif (!editable.has(ref.nodeId)) continue;\n\t\tconst node = findVectorNode(state.document.nodes, ref.nodeId) as VectorPathNode;\n\t\tconst sp = node.subpaths[ref.subpath];\n\t\tif (!sp) continue;\n\t\tconst n = sp.anchors.length;\n\t\tpush(node, ref.subpath, ref.index, 'in');\n\t\tpush(node, ref.subpath, ref.index, 'out');\n\t\tconst prev = ref.index > 0 ? ref.index - 1 : sp.closed ? n - 1 : -1;\n\t\tconst next = ref.index < n - 1 ? ref.index + 1 : sp.closed ? 0 : -1;\n\t\tif (prev >= 0) push(node, ref.subpath, prev, 'out');\n\t\tif (next >= 0) push(node, ref.subpath, next, 'in');\n\t}\n\treturn out;\n}\n\n/** Live corner widgets sit at least this far inside their corner (screen px along each side of a right angle). */\nexport const VECTOR_CORNER_WIDGET_INSET = 10;\n\n/** A live corner widget of the direct tool: drag it to round the corners, Alt-click to change their kind. */\nexport interface VectorCornerWidget {\n\tref: VectorAnchorRef;\n\t/** Document position of the widget. */\n\tx: number;\n\ty: number;\n\t/** The radius stored on the anchor and the radius drawn (no larger than fits). */\n\tradius: number;\n\tdrawnRadius: number;\n\tkind: VectorCornerKind;\n\t/** The corner in its node's own space. */\n\tcorner: VectorCornerInfo;\n}\n\n/**\n * The live corner widgets the direct tool shows: on each editable path, at its selected corner\n * anchors (or at every corner when none of its anchors is selected). Corners whose sides are\n * shorter than three insets on screen hide theirs. Dragging one sets the radius of all of them.\n * Ported from ArtCraft vectorcraft `crates/tools/src/corners.rs` (MIT OR Apache-2.0, see\n * THIRD_PARTY_NOTICES.md), as is the `corner` drag below.\n */\nexport function getVectorCornerWidgets(\n\tstate: Pick<VectorEditorState, 'document' | 'selection' | 'editingId' | 'tool' | 'pen' | 'selectedAnchors' | 'viewport'>\n): VectorCornerWidget[] {\n\tif (state.tool !== 'direct' || state.pen) return [];\n\tconst zoom = state.viewport.zoom;\n\tconst out: VectorCornerWidget[] = [];\n\tfor (const id of getEditablePathIds(state)) {\n\t\tconst node = findVectorNode(state.document.nodes, id) as VectorPathNode;\n\t\tconst picked = state.selectedAnchors.filter((r) => r.nodeId === id);\n\t\tconst m = vectorWorldMatrix(state.document.nodes, id) ?? IDENTITY_MATRIX;\n\t\tfor (const c of vectorCorners(node.subpaths)) {\n\t\t\tif (picked.length && !picked.some((r) => r.subpath === c.subpath && r.index === c.index)) continue;\n\t\t\tconst side = Math.min(c.lu, c.lv);\n\t\t\tif (side * zoom < 3 * VECTOR_CORNER_WIDGET_INSET) continue;\n\t\t\tconst a = node.subpaths[c.subpath].anchors[c.index];\n\t\t\tconst radius = a.corner?.radius ?? 0;\n\t\t\tconst drawn = fitCornerRadius(c, radius);\n\t\t\t// On the bisector at the centre of the arc as drawn, at least the inset in and at most half the shorter side.\n\t\t\tconst k = Math.min(Math.max(drawn / c.sin, VECTOR_CORNER_WIDGET_INSET / (zoom * Math.sqrt(1 + c.cos))), side / 2);\n\t\t\tconst p = applyMatrix(m, cornerBisectorPoint(c, k));\n\t\t\tout.push({ ref: { nodeId: id, subpath: c.subpath, index: c.index }, x: p.x, y: p.y, radius, drawnRadius: drawn, kind: a.corner?.kind ?? 'round', corner: c });\n\t\t}\n\t}\n\treturn out;\n}\n\n/**\n * The line a path's stroke follows (node space; shortened under `tip` arrowheads) and the\n * distance a side scale of 1 reaches from it (half the drawn width: twice the stroke width for\n * inside / outside strokes). Null for paths without a visible stroke.\n */\nexport function vectorStrokeWidthGeometry(node: VectorPathNode): { line: BezierPath; unit: number } | null {\n\tif (!hasPaint(node.stroke) || !((node.strokeWidth ?? 1) > 0)) return null;\n\tconst parts = vectorStrokeParts(subpathsToBezier(node.subpaths), node);\n\treturn parts.line.length ? { line: parts.line, unit: parts.drawWidth / 2 } : null;\n}\n\n/** A width point of the width tool, in document units. */\nexport interface VectorWidthHandle {\n\tref: VectorWidthPointRef;\n\t/** The point on the path. */\n\tx: number;\n\ty: number;\n\t/** Ends of its left and right sides. */\n\tleft: Vec2;\n\tright: Vec2;\n\tt: number;\n\t/** Side scales (multiples of half the stroke width). */\n\tleftScale: number;\n\trightScale: number;\n\tselected: boolean;\n}\n\n/**\n * Moves the points at `indices` of `profile` by `dt` along the path (kept in 0–1) and re-sorts\n * it (stably, so steps keep their order); `renumber` maps each old index to its new one.\n */\nfunction shiftWidthPoints(profile: readonly VectorWidthPoint[], indices: readonly number[], dt: number): { points: VectorWidthPoint[]; renumber: Map<number, number> } {\n\tconst moving = new Set(indices);\n\tconst tagged = profile.map((p, i) => ({ p: moving.has(i) ? { ...p, t: Math.min(Math.max(p.t + dt, 0), 1) } : { ...p }, i }));\n\ttagged.sort((a, b) => a.p.t - b.p.t || a.i - b.i);\n\treturn { points: tagged.map((x) => x.p), renumber: new Map(tagged.map((x, k) => [x.i, k])) };\n}\n\n/**\n * The width points the width tool shows: every profile point on every subpath of the stroke\n * under the pointer, the selected paths and the stroke being edited. Ported from ArtCraft\n * vectorcraft `crates/tools/src/distort/width.rs` (MIT OR Apache-2.0, see\n * THIRD_PARTY_NOTICES.md), as are the `width` drag and hover below.\n */\nexport function getVectorWidthHandles(\n\tstate: Pick<VectorEditorState, 'document' | 'selection' | 'tool' | 'hover' | 'interaction' | 'selectedWidthPoints'>\n): VectorWidthHandle[] {\n\tif (state.tool !== 'width') return [];\n\tconst ids = new Set<string>();\n\tfor (const id of state.selection) {\n\t\tconst n = findVectorNode(state.document.nodes, id);\n\t\tif (n) for (const p of collectPathNodes(n)) ids.add(p.id);\n\t}\n\tif (state.hover?.nodeId) ids.add(state.hover.nodeId);\n\tif (state.interaction?.type === 'width') ids.add(state.interaction.ref.nodeId);\n\tfor (const r of state.selectedWidthPoints) ids.add(r.nodeId);\n\tconst out: VectorWidthHandle[] = [];\n\tfor (const id of ids) {\n\t\tconst node = findVectorNode(state.document.nodes, id);\n\t\tif (node?.type !== 'path' || node.visible === false || !node.strokeWidthProfile?.length) continue;\n\t\tconst g = vectorStrokeWidthGeometry(node);\n\t\tif (!g) continue;\n\t\tconst profile = sanitizeWidthProfile(node.strokeWidthProfile);\n\t\tconst m = vectorWorldMatrix(state.document.nodes, id) ?? IDENTITY_MATRIX;\n\t\tconst contours = node.subpaths.length;\n\t\tfor (let sp = 0; sp < contours; sp++) {\n\t\t\tprofile.forEach((w, index) => {\n\t\t\t\tconst at = strokePositionPoint(g.line, sp, w.t);\n\t\t\t\tif (!at) return;\n\t\t\t\tconst n = { x: at.tangent[1], y: -at.tangent[0] };\n\t\t\t\tconst c = { x: at.point[0], y: at.point[1] };\n\t\t\t\tconst p = applyMatrix(m, c);\n\t\t\t\tconst l = applyMatrix(m, { x: c.x + n.x * w.left * g.unit, y: c.y + n.y * w.left * g.unit });\n\t\t\t\tconst r = applyMatrix(m, { x: c.x - n.x * w.right * g.unit, y: c.y - n.y * w.right * g.unit });\n\t\t\t\tout.push({\n\t\t\t\t\tref: { nodeId: id, index, subpath: sp },\n\t\t\t\t\tx: p.x,\n\t\t\t\t\ty: p.y,\n\t\t\t\t\tleft: l,\n\t\t\t\t\tright: r,\n\t\t\t\t\tt: w.t,\n\t\t\t\t\tleftScale: w.left,\n\t\t\t\t\trightScale: w.right,\n\t\t\t\t\tselected: state.selectedWidthPoints.some((r) => r.nodeId === id && r.index === index)\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Looks up an anchor (positions in its node's own space). */\nexport function getAnchor(doc: VectorDocument, ref: VectorAnchorRef): VectorAnchor | undefined {\n\tconst node = findVectorNode(doc.nodes, ref.nodeId);\n\treturn node?.type === 'path' ? node.subpaths[ref.subpath]?.anchors[ref.index] : undefined;\n}\n\n/** An anchor with its position and handles in document space (its node's transforms applied). */\nexport function getAnchorWorld(doc: VectorDocument, ref: VectorAnchorRef): { x: number; y: number; in?: Vec2; out?: Vec2; type?: VectorAnchorType } | undefined {\n\tconst a = getAnchor(doc, ref);\n\tif (!a) return undefined;\n\tconst m = vectorWorldMatrix(doc.nodes, ref.nodeId) ?? IDENTITY_MATRIX;\n\tconst p = applyMatrix(m, a);\n\treturn { x: p.x, y: p.y, ...(a.in ? { in: applyMatrix(m, a.in) } : {}), ...(a.out ? { out: applyMatrix(m, a.out) } : {}), ...(a.type ? { type: a.type } : {}) };\n}\n\n/**\n * The selection box: `angle` is the shared rotation of the selected nodes (0 when they\n * differ), `x / y / width / height` the bounds in the box's own frame (document space\n * rotated by −angle). A frame point `q` is at `rotateMatrix(angle)` applied to `q`.\n */\nexport interface VectorSelectionFrame {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\tangle: number;\n}\n\n/** The selection frame of nodes `ids` (see `VectorSelectionFrame`), or null when they have no points. */\nexport function getVectorSelectionFrame(doc: VectorDocument, ids: readonly string[]): VectorSelectionFrame | null {\n\tconst nodes = ids.map((id) => ({ node: findVectorNode(doc.nodes, id), parent: vectorParentMatrix(doc.nodes, id) ?? IDENTITY_MATRIX })).filter((n) => n.node);\n\tif (!nodes.length) return null;\n\tconst angles = nodes.map((n) => matrixRotation(multiplyMatrices(nodeMatrix(n.node!), n.parent)));\n\tconst same = angles.every((a) => Math.abs(normalizeAngle(a - angles[0])) < 1e-6);\n\tconst angle = same ? angles[0] : 0;\n\tconst unrotate = rotateMatrix(-angle);\n\tconst b = unionBounds(nodes.map((n) => vectorNodeBounds(n.node!, multiplyMatrices(n.parent, unrotate))));\n\treturn b ? { ...b, angle } : null;\n}\n\nconst normalizeAngle = (a: number) => ((((a + 180) % 360) + 360) % 360) - 180;\n\n/** Screen positions of the selection box handles for a box given in screen pixels. */\nexport function getTransformHandlePositions(box: VectorBounds): { handle: VectorTransformHandle; x: number; y: number }[] {\n\tconst { x, y, width: w, height: h } = box;\n\treturn [\n\t\t{ handle: 'nw', x, y },\n\t\t{ handle: 'n', x: x + w / 2, y },\n\t\t{ handle: 'ne', x: x + w, y },\n\t\t{ handle: 'e', x: x + w, y: y + h / 2 },\n\t\t{ handle: 'se', x: x + w, y: y + h },\n\t\t{ handle: 's', x: x + w / 2, y: y + h },\n\t\t{ handle: 'sw', x, y: y + h },\n\t\t{ handle: 'w', x, y: y + h / 2 },\n\t\t{ handle: 'rotate', x: x + w / 2, y: y - VECTOR_ROTATE_HANDLE_OFFSET }\n\t];\n}\n\n/**\n * Screen geometry of a selection box. `box` and each handle's `x / y` are the unrotated box\n * in screen pixels; draw them inside a group with `transform` (a `rotate(angle cx cy)`),\n * or use `screen` for the true on-screen positions (hit-testing).\n */\nexport interface VectorSelectionBoxGeometry {\n\tbox: VectorBounds;\n\tangle: number;\n\ttransform: string;\n\thandles: { handle: VectorTransformHandle; x: number; y: number; screen: Vec2 }[];\n\t/** The four corners on screen, clockwise from the top left. */\n\tcorners: Vec2[];\n}\n\nexport function getVectorSelectionBoxGeometry(frame: VectorSelectionFrame, viewport: VectorViewport): VectorSelectionBoxGeometry {\n\tconst z = viewport.zoom;\n\tconst box = { x: frame.x * z + viewport.x, y: frame.y * z + viewport.y, width: frame.width * z, height: frame.height * z };\n\tconst rot = rotateMatrix(frame.angle, { x: viewport.x, y: viewport.y });\n\tconst toScreen = (p: Vec2): Vec2 => (frame.angle ? applyMatrix(rot, p) : { x: p.x, y: p.y });\n\tconst handles = getTransformHandlePositions(box).map((h) => ({ ...h, screen: toScreen(h) }));\n\tconst corners = [\n\t\t{ x: box.x, y: box.y },\n\t\t{ x: box.x + box.width, y: box.y },\n\t\t{ x: box.x + box.width, y: box.y + box.height },\n\t\t{ x: box.x, y: box.y + box.height }\n\t].map(toScreen);\n\tconst r = (n: number) => Math.round(n * 1000) / 1000;\n\treturn { box, angle: frame.angle, transform: frame.angle ? `rotate(${r(frame.angle)} ${r(viewport.x)} ${r(viewport.y)})` : '', handles, corners };\n}\n\nconst RESIZE_CURSOR_ORDER = ['ew-resize', 'nwse-resize', 'ns-resize', 'nesw-resize'];\nconst HANDLE_ANGLES: Partial<Record<VectorTransformHandle, number>> = { e: 0, se: 45, s: 90, sw: 135, w: 180, nw: 225, n: 270, ne: 315 };\n\n/** The resize cursor for a handle of a box rotated by `angle` degrees. */\nexport function vectorResizeCursor(handle: VectorTransformHandle, angle = 0): string {\n\tif (handle === 'rotate') return 'grab';\n\tconst a = (((HANDLE_ANGLES[handle]! + angle) % 180) + 180) % 180;\n\treturn RESIZE_CURSOR_ORDER[Math.round(a / 45) % 4];\n}\n\n\nconst TOOL_LABELS: Record<VectorTool, string> = {\n\tselect: 'Select',\n\tdirect: 'Direct Select',\n\tpen: 'Pen',\n\tpencil: 'Pencil',\n\trect: 'Rectangle',\n\tellipse: 'Ellipse',\n\tpolygon: 'Polygon',\n\tstar: 'Star',\n\tline: 'Line',\n\tscissors: 'Scissors',\n\tknife: 'Knife',\n\teraser: 'Eraser',\n\twidth: 'Width',\n\tshapeBuilder: 'Shape Builder',\n\tpaintBucket: 'Paint Bucket',\n\tblend: 'Blend',\n\tgradient: 'Gradient'\n};\n\nconst TOOL_KEYS: Record<string, VectorTool> = {\n\tv: 'select',\n\ta: 'direct',\n\tp: 'pen',\n\tn: 'pencil',\n\tr: 'rect',\n\to: 'ellipse',\n\ty: 'polygon',\n\ts: 'star',\n\tl: 'line',\n\tc: 'scissors',\n\tk: 'knife',\n\te: 'eraser',\n\tw: 'width',\n\tm: 'shapeBuilder',\n\tb: 'paintBucket',\n\tj: 'blend',\n\tg: 'gradient'\n};\n\nexport function vectorToolLabel(tool: VectorTool): string {\n\treturn TOOL_LABELS[tool];\n}\n\n/** Keyboard shortcut of each tool (single letters, no modifiers). */\nexport const VECTOR_TOOL_SHORTCUTS: Readonly<Record<VectorTool, string>> = {\n\tselect: 'V',\n\tdirect: 'A',\n\tpen: 'P',\n\tpencil: 'N',\n\trect: 'R',\n\tellipse: 'O',\n\tpolygon: 'Y',\n\tstar: 'S',\n\tline: 'L',\n\tscissors: 'C',\n\tknife: 'K',\n\teraser: 'E',\n\twidth: 'W',\n\tshapeBuilder: 'M',\n\tpaintBucket: 'B',\n\tblend: 'J',\n\tgradient: 'G'\n};\n\n/** Short glyphs for tool buttons (pair them with `vectorToolLabel` for the accessible name). */\nexport const VECTOR_TOOL_GLYPHS: Readonly<Record<VectorTool, string>> = {\n\tselect: '↖',\n\tdirect: '⌖',\n\tpen: '✒',\n\tpencil: '✎',\n\trect: '▭',\n\tellipse: '◯',\n\tpolygon: '⬡',\n\tstar: '☆',\n\tline: '╱',\n\tscissors: '✂',\n\tknife: '⟋',\n\teraser: '⌫',\n\twidth: '⇹',\n\tshapeBuilder: '⊕',\n\tpaintBucket: '◍',\n\tblend: '⧓',\n\tgradient: '◐'\n};\n\n/** Tools that drag out a shape (they snap while hovering). */\nconst SHAPE_TOOLS: ReadonlySet<VectorTool> = new Set(['rect', 'ellipse', 'polygon', 'star', 'line']);\n\nconst BOOLEAN_OPS: Record<VectorBooleanOperation, 'union' | 'difference' | 'intersection' | 'xor'> = {\n\tunion: 'union',\n\tsubtract: 'difference',\n\tintersect: 'intersection',\n\texclude: 'xor'\n};\n\nconst BOOLEAN_LABELS: Record<VectorBooleanOperation, string> = { union: 'Union', subtract: 'Subtract', intersect: 'Intersect', exclude: 'Exclude' };\n\n/** Names of the Pathfinder operations (undo steps, groups and buttons). */\nexport const VECTOR_PATHFINDER_LABELS: Readonly<Record<VectorPathfinderOperation, string>> = {\n\tdivide: 'Divide',\n\ttrim: 'Trim',\n\tmerge: 'Merge',\n\tcrop: 'Crop',\n\toutline: 'Outline',\n\tminusBack: 'Minus Back'\n};\n\n/** Replaces nodes by id anywhere in the tree (structural sharing for untouched branches). */\nfunction mapTree(nodes: readonly VectorNode[], fn: (node: VectorNode) => VectorNode | null): VectorNode[] {\n\tlet changed = false;\n\tconst out: VectorNode[] = [];\n\tfor (const node of nodes) {\n\t\tlet next = fn(node);\n\t\tif (next && next.type === 'group') {\n\t\t\tconst children = mapTree(next.children, fn);\n\t\t\tif (children !== next.children) next = { ...next, children };\n\t\t}\n\t\tif (next !== node) changed = true;\n\t\tif (next) out.push(next);\n\t}\n\treturn changed ? out : (nodes as VectorNode[]);\n}\n\n/** Replaces node `id` anywhere in the tree with `replacements` (none removes it). */\nfunction spliceTree(nodes: readonly VectorNode[], id: string, replacements: readonly VectorNode[]): VectorNode[] {\n\tlet changed = false;\n\tconst out: VectorNode[] = [];\n\tfor (const node of nodes) {\n\t\tif (node.id === id) {\n\t\t\tchanged = true;\n\t\t\tout.push(...replacements);\n\t\t\tcontinue;\n\t\t}\n\t\tif (node.type === 'group') {\n\t\t\tconst children = spliceTree(node.children, id, replacements);\n\t\t\tif (children !== node.children) {\n\t\t\t\tchanged = true;\n\t\t\t\tout.push({ ...node, children });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tout.push(node);\n\t}\n\treturn changed ? out : (nodes as VectorNode[]);\n}\n\nconst toPoint = (p: Vec2): Point => [p.x, p.y];\n\n/** A node's place and state (transform, visibility, lock, operand operation), for a node taking its place. */\nconst placementOf = (n: VectorNode) => ({\n\t...(n.transform ? { transform: n.transform } : {}),\n\t...(n.visible === false ? { visible: false } : {}),\n\t...(n.locked ? { locked: true } : {}),\n\t...(n.booleanOperation ? { booleanOperation: n.booleanOperation } : {})\n});\n\nconst isRoot = (m: AffineMatrix) => m === IDENTITY_MATRIX || (m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0);\n\nlet idCounter = 0;\nconst defaultCreateId = () => `vn-${Date.now().toString(36)}${(++idCounter).toString(36)}`;\n\n// ---------------------------------------------------------------------------\n// Controller\n// ---------------------------------------------------------------------------\n\ninterface Snapshot {\n\tdoc: VectorDocument;\n\tselection: string[];\n\tanchors: VectorAnchorRef[];\n\teditingId: string | null;\n\tpen: { nodeId: string; subpath: number } | null;\n}\n\n/** Snap targets of a drag, gathered on its first move that needs them. */\ntype DragTargets = { targets?: VectorSnapTargets };\n\ntype Drag =\n\t| ({ kind: 'move'; start: Vec2; origin: VectorBounds | null; before: Snapshot; base: VectorDocument; moved: boolean; duplicated: boolean; clickId: string | null } & DragTargets)\n\t| ({ kind: 'scale'; handle: VectorTransformHandle; start: Vec2; frame: VectorSelectionFrame; before: Snapshot; base: VectorDocument } & DragTargets)\n\t| { kind: 'rotate'; start: Vec2; center: Vec2; before: Snapshot; base: VectorDocument }\n\t| { kind: 'marquee'; start: Vec2; base: string[]; baseAnchors: VectorAnchorRef[]; anchorsMode: boolean }\n\t| ({ kind: 'anchors'; start: Vec2; before: Snapshot; base: VectorDocument; moved: boolean; clickRef: VectorAnchorRef | null; lead: VectorAnchorRef | null } & DragTargets)\n\t| ({ kind: 'handle'; ref: VectorHandleRef; before: Snapshot; base: VectorDocument } & DragTargets)\n\t| { kind: 'bend'; ref: VectorSegmentRef; t: number; start: Vec2; before: Snapshot; base: VectorDocument; moved: boolean }\n\t| { kind: 'corner'; widget: VectorCornerWidget; refs: VectorAnchorRef[]; start: Vec2; before: Snapshot; base: VectorDocument; moved: boolean; alt: boolean; shift: boolean }\n\t| { kind: 'stroke'; tool: 'knife' | 'eraser'; points: Vec2[]; before: Snapshot }\n\t| { kind: 'builder'; erase: boolean; points: Vec2[]; touched: VectorBuilderTouched; map: VectorShapeBuilderMap }\n\t| { kind: 'pull'; ref: VectorAnchorRef; before: Snapshot; base: VectorDocument; label: string; outOnly: boolean; closing: boolean; altBreaks: boolean }\n\t| ({ kind: 'shape'; tool: VectorTool; start: Vec2; nodeId: string; before: Snapshot } & DragTargets)\n\t| { kind: 'pencil'; nodeId: string; points: Point[]; before: Snapshot; lazy: LazyMouse; pressures: number[] | null; pressure: number }\n\t| {\n\t\t\tkind: 'width';\n\t\t\tref: VectorWidthPointRef;\n\t\t\t/** `copy`: Alt-drag a centre to slide a copy of the point. */\n\t\t\tmode: 'width' | 'move' | 'copy';\n\t\t\tside: 'both' | 'left' | 'right';\n\t\t\t/** The index the drag started on (the source of a copy). */\n\t\t\tsource: number;\n\t\t\t/** Other selected points of the path that move or widen with it (indices into `profile`). */\n\t\t\tgroup: number[];\n\t\t\t/** Shift-click (adding the point): a click without a move keeps the other selected points. */\n\t\t\tshift: boolean;\n\t\t\t/** Old index → new index after a group move re-sorted the profile. */\n\t\t\trenumber: Map<number, number> | null;\n\t\t\t/** The stroke line's point and left normal where the drag started (node space). */\n\t\t\torigin: Vec2;\n\t\t\tnormal: Vec2;\n\t\t\t/** Half the drawn stroke width: side scale 1 reaches this far. */\n\t\t\tunit: number;\n\t\t\tprofile: VectorWidthPoint[];\n\t\t\tbefore: Snapshot;\n\t\t\tbase: VectorDocument;\n\t\t\tstart: Vec2;\n\t\t\tmoved: boolean;\n\t\t\tcreated: boolean;\n\t }\n\t| {\n\t\t\tkind: 'gradient';\n\t\t\t/** What was grabbed: null draws a new vector over `targets`. */\n\t\t\tpart: VectorGradientPart | null;\n\t\t\tnodeId: string | null;\n\t\t\ttargets: string[];\n\t\t\tannotator: VectorGradientAnnotator | null;\n\t\t\tstart: Vec2;\n\t\t\tstartDoc: Vec2;\n\t\t\tbefore: Snapshot;\n\t\t\tbase: VectorDocument;\n\t\t\tmoved: boolean;\n\t\t\t/** The dragged stop's index now (null once dragged off the bar). */\n\t\t\tstop: number | null;\n\t }\n\t| { kind: 'pan'; start: Vec2; viewport: VectorViewport };\n\n/**\n * Headless state for a vector editor: a document of paths and groups, select / direct /\n * pen / pencil / shape tools driven by pointer and key events, undo, transforms, boolean\n * operations, alignment, grouping, SVG import / export and a viewport. Any UI forwards\n * pointer events (in canvas pixels) and renders `getState()`; `getState()` returns the same\n * object until something changes, so it works with `useSyncExternalStore` and Svelte runes.\n */\nexport class VectorEditorController {\n\treadonly history: HistoryManager;\n\tprivate doc: VectorDocument;\n\tprivate selection: string[] = [];\n\tprivate anchors: VectorAnchorRef[] = [];\n\tprivate widthPoints: VectorWidthPointRef[] = [];\n\tprivate widthEdit: VectorWidthPointEdit | null = null;\n\tprivate editingId: string | null = null;\n\tprivate pen: { nodeId: string; subpath: number } | null = null;\n\tprivate tool: VectorTool;\n\tprivate defaults: VectorEditorState['defaults'];\n\tprivate viewport: VectorViewport;\n\tprivate snap: VectorSnapOptions;\n\tprivate editing: VectorPathEditOptions;\n\tprivate hitRadius: number;\n\tprivate hover: VectorHover | null = null;\n\tprivate pointer: Vec2 | null = null;\n\tprivate drag: Drag | null = null;\n\tprivate interaction: VectorInteraction | null = null;\n\tprivate guides: VectorSnapGuides | null = null;\n\t/** Whether the last `snapDrawn` landed on an object (guides may be hidden). */\n\tprivate snappedToObject = false;\n\t/** Drawing targets for hovering the pen and shape tools, kept while the document and the excluded path stay the same. */\n\tprivate hoverTargets: { doc: VectorDocument; exclude: string; targets: VectorSnapTargets } | null = null;\n\tprivate spaceHeld = false;\n\tprivate clipboard: VectorNode[] | null = null;\n\t/** Shape Builder map of the selection, kept while the document and the selection stay the same. */\n\tprivate builderCache: { doc: VectorDocument; key: string; map: VectorShapeBuilderMap | null } | null = null;\n\t/** Paint bucket map of the visible outlines. */\n\tprivate fillCache: { doc: VectorDocument; gap: number; map: PlanarMap | null } | null = null;\n\tprivate builderPreview: VectorBuilderPreview | null = null;\n\tprivate blendStart: VectorBlendStart | null = null;\n\t/** Options new blends get (the toolbars edit them along with the selected blend's). */\n\tprivate blendDefaults: VectorBlendOptions = {};\n\tprivate gradientEdit: VectorEditorState['gradientEdit'] = { target: 'fill', stop: null };\n\t/** What the highlight shows, to skip notifying when the pointer stays over the same face. */\n\tprivate builderHoverKey: string | null = null;\n\tprivate readonly createId: () => string;\n\tprivate readonly onChange?: (document: VectorDocument) => void;\n\tprivate readonly listeners = new Set<(state: VectorEditorState) => void>();\n\tprivate readonly cleanups: Array<() => void> = [];\n\tprivate applying = false;\n\t/** Set while a commit records a change that was already applied, so `restore` still reports it. */\n\tprivate committing = false;\n\tprivate revision = 0;\n\tprivate state!: VectorEditorState;\n\n\tconstructor(options: VectorEditorOptions = {}) {\n\t\tthis.history = options.history ?? new HistoryManager();\n\t\tthis.doc = options.document ? cloneVectorDocument(options.document) : { width: 800, height: 600, nodes: [] };\n\t\tthis.doc = this.refreshed(this.doc);\n\t\tthis.tool = options.tool ?? 'select';\n\t\tthis.defaults = { ...DEFAULT_DEFAULTS, ...(options.defaults ?? {}) } as VectorEditorState['defaults'];\n\t\tthis.viewport = { x: 0, y: 0, zoom: 1, ...(options.viewport ?? {}) };\n\t\tthis.snap = { ...DEFAULT_VECTOR_SNAP, ...(options.snap ?? {}) };\n\t\tthis.editing = { ...DEFAULT_VECTOR_PATH_EDIT, ...(options.editing ?? {}) };\n\t\tthis.hitRadius = options.hitRadius ?? 6;\n\t\tthis.createId = options.createId ?? defaultCreateId;\n\t\tthis.onChange = options.onChange;\n\t\tthis.state = this.computeState();\n\t\tthis.connect();\n\t}\n\n\t/** Listens to the history (once; again after `destroy`). */\n\tprivate connect(): void {\n\t\tif (this.cleanups.length) return;\n\t\tthis.cleanups.push(\n\t\t\tthis.history.subscribe((h) => {\n\t\t\t\tif (!this.applying && this.historyDiffers(h)) this.notify();\n\t\t\t})\n\t\t);\n\t\t// Catch up on history changes made while disconnected.\n\t\tif (this.historyDiffers(this.history.getState())) this.state = this.computeState();\n\t}\n\n\tprivate historyDiffers(h: ReturnType<HistoryManager['getState']>): boolean {\n\t\tconst s = this.state;\n\t\treturn s.canUndo !== h.canUndo || s.canRedo !== h.canRedo || s.undoLabel !== h.undoName || s.redoLabel !== h.redoName;\n\t}\n\n\t/**\n\t * Stops listening to the history and drops all listeners. A later `subscribe` reconnects, so a\n\t * controller torn down by React StrictMode's double effect keeps working when it is reused.\n\t */\n\tdestroy(): void {\n\t\tthis.cleanups.splice(0).forEach((fn) => fn());\n\t\tthis.listeners.clear();\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// State\n\t// -------------------------------------------------------------------------\n\n\t/** The current snapshot (stable until the next change). */\n\tgetState = (): VectorEditorState => this.state;\n\n\t/** Calls `listener` after every change. Returns an unsubscribe function. */\n\tsubscribe = (listener: (state: VectorEditorState) => void): (() => void) => {\n\t\tthis.connect();\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t};\n\n\tgetDocument(): VectorDocument {\n\t\treturn this.doc;\n\t}\n\n\tgetNode(id: string): VectorNode | undefined {\n\t\treturn findVectorNode(this.doc.nodes, id);\n\t}\n\n\tprivate computeState(): VectorEditorState {\n\t\tconst h = this.history.getState();\n\t\treturn {\n\t\t\tdocument: this.doc,\n\t\t\ttool: this.tool,\n\t\t\tselection: this.selection,\n\t\t\tselectedAnchors: this.anchors,\n\t\t\tselectedWidthPoints: this.widthPoints,\n\t\t\twidthPointEdit: this.widthEdit,\n\t\t\teditingId: this.editingId,\n\t\t\tselectionBounds: this.boundsOf(this.selection),\n\t\t\tselectionFrame: this.frameOf(this.selectedRoots()),\n\t\t\tviewport: this.viewport,\n\t\t\tdefaults: this.defaults,\n\t\t\tsnap: this.snap,\n\t\t\tediting: this.editing,\n\t\t\tguides: this.guides,\n\t\t\thitRadius: this.hitRadius,\n\t\t\thover: this.hover,\n\t\t\tcursor: this.cursorFor(),\n\t\t\tinteraction: this.interaction,\n\t\t\tpen: this.pen,\n\t\t\tpointer: this.pointer,\n\t\t\tbuilder: this.builderPreview,\n\t\t\tblendStart: this.blendStart,\n\t\t\tblendDefaults: this.blendDefaults,\n\t\t\tgradientEdit: this.gradientEdit,\n\t\t\tcanUndo: h.canUndo,\n\t\t\tcanRedo: h.canRedo,\n\t\t\tundoLabel: h.undoName,\n\t\t\tredoLabel: h.redoName,\n\t\t\trevision: this.revision\n\t\t};\n\t}\n\n\t/** `doc` with its blends' steps brought up to date (the same object when they already are). */\n\tprivate refreshed(doc: VectorDocument): VectorDocument {\n\t\tconst nodes = refreshVectorBlends(doc.nodes);\n\t\treturn nodes === doc.nodes ? doc : { ...doc, nodes };\n\t}\n\n\tprivate notify(docChanged = false) {\n\t\t// Blends regenerate their steps after every edit of a key (drags included).\n\t\tconst fresh = this.refreshed(this.doc);\n\t\tif (fresh !== this.doc) this.doc = fresh;\n\t\tif (this.blendStart && !this.getNode(this.blendStart.nodeId)) this.blendStart = null;\n\t\tthis.revision++;\n\t\tthis.state = this.computeState();\n\t\tif (docChanged) this.onChange?.(this.doc);\n\t\tconst state = this.state;\n\t\tthis.listeners.forEach((listener) => {\n\t\t\ttry {\n\t\t\t\tlistener(state);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[VectorEditorController] Error in state listener:', error);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate boundsOf(ids: readonly string[]): VectorBounds | null {\n\t\treturn unionBounds(\n\t\t\tids.map((id) => {\n\t\t\t\tconst n = this.getNode(id);\n\t\t\t\treturn n ? vectorNodeBounds(n, vectorParentMatrix(this.doc.nodes, id) ?? IDENTITY_MATRIX) : null;\n\t\t\t})\n\t\t);\n\t}\n\n\tprivate frameOf(ids: readonly string[]): VectorSelectionFrame | null {\n\t\treturn getVectorSelectionFrame(this.doc, ids);\n\t}\n\n\t/** Local → document matrix of a node in `doc` (default: the current document). */\n\tprivate worldOf(id: string, doc: VectorDocument = this.doc): AffineMatrix {\n\t\treturn vectorWorldMatrix(doc.nodes, id) ?? IDENTITY_MATRIX;\n\t}\n\n\t/** A document point in node `id`'s own space. */\n\tprivate toLocal(id: string, p: Vec2, doc: VectorDocument = this.doc): Vec2 {\n\t\treturn applyMatrix(invertMatrix(this.worldOf(id, doc)), p);\n\t}\n\n\tprivate cursorFor(): string {\n\t\tif (this.drag?.kind === 'pan' || this.spaceHeld) return this.drag ? 'grabbing' : 'grab';\n\t\tif (this.drag?.kind === 'rotate') return 'grabbing';\n\t\tif (this.drag?.kind === 'move') return 'move';\n\t\tconst h = this.hover;\n\t\tswitch (this.tool) {\n\t\t\tcase 'select':\n\t\t\t\tif (h?.transformHandle) return vectorResizeCursor(h.transformHandle, this.frameOf(this.selectedRoots())?.angle ?? 0);\n\t\t\t\treturn h?.nodeId ? 'move' : 'default';\n\t\t\tcase 'direct':\n\t\t\t\tif (h?.anchor || h?.handle || h?.corner) return 'pointer';\n\t\t\t\tif (h?.segment) return 'copy';\n\t\t\t\treturn h?.nodeId ? 'move' : 'default';\n\t\t\tcase 'pen':\n\t\t\t\treturn h?.anchor ? 'pointer' : 'crosshair';\n\t\t\tcase 'width':\n\t\t\t\tif (this.drag?.kind === 'width') return this.drag.mode === 'width' ? 'ew-resize' : 'grabbing';\n\t\t\t\tif (h?.widthPoint) return h.widthPoint.part === 'center' ? 'grab' : 'ew-resize';\n\t\t\t\treturn h?.strokeSpot ? 'copy' : 'default';\n\t\t\tcase 'shapeBuilder':\n\t\t\t\tif (!this.builderPreview?.faces.length && !this.builderPreview?.lines.length) return 'crosshair';\n\t\t\t\treturn this.builderPreview.mode === 'erase' ? 'not-allowed' : 'cell';\n\t\t\tcase 'paintBucket':\n\t\t\t\treturn this.builderPreview ? 'cell' : 'crosshair';\n\t\t\tcase 'blend':\n\t\t\t\treturn h?.nodeId ? (this.blendStart && this.blendStart.nodeId !== h.nodeId ? 'copy' : 'pointer') : 'crosshair';\n\t\t\tcase 'gradient': {\n\t\t\t\tconst part = this.drag?.kind === 'gradient' ? this.drag.part : h?.gradient;\n\t\t\t\tif (!part) return 'crosshair';\n\t\t\t\tif (part.kind === 'bar') return this.drag ? 'grabbing' : 'copy';\n\t\t\t\tif (part.kind === 'handle' && part.handle === 'start') return this.drag ? 'grabbing' : 'move';\n\t\t\t\treturn 'pointer';\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn 'crosshair';\n\t\t}\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Undoable changes\n\t// -------------------------------------------------------------------------\n\n\tprivate snapshot(): Snapshot {\n\t\treturn { doc: this.doc, selection: this.selection, anchors: this.anchors, editingId: this.editingId, pen: this.pen };\n\t}\n\n\tprivate restore(s: Snapshot) {\n\t\tconst docChanged = s.doc !== this.doc || this.committing;\n\t\t// Undo / redo can renumber width points: drop the ones that are gone and close the editor.\n\t\tif (!this.committing) this.widthEdit = null;\n\t\tthis.committing = false;\n\t\tthis.applying = true;\n\t\ttry {\n\t\t\tthis.doc = s.doc;\n\t\t\tthis.selection = s.selection.filter((id) => this.getNode(id));\n\t\t\tthis.anchors = s.anchors.filter((r) => getAnchor(this.doc, r));\n\t\t\tthis.editingId = s.editingId && this.getNode(s.editingId) ? s.editingId : null;\n\t\t\tthis.pen = s.pen && (this.getNode(s.pen.nodeId) as VectorPathNode | undefined)?.subpaths?.[s.pen.subpath] ? s.pen : null;\n\t\t\tif (this.widthPoints.length) {\n\t\t\t\tconst kept = this.widthPoints.filter((r) => {\n\t\t\t\t\tconst n = this.getNode(r.nodeId);\n\t\t\t\t\treturn n?.type === 'path' && !!n.strokeWidthProfile?.[r.index];\n\t\t\t\t});\n\t\t\t\tif (kept.length !== this.widthPoints.length) this.widthPoints = kept;\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.applying = false;\n\t\t}\n\t\tthis.notify(docChanged);\n\t}\n\n\t/**\n\t * Records the change from `before` to the current state as one undo step. Consecutive\n\t * changes with the same `merge` key (a slider drag) fold into one step.\n\t */\n\tprivate commit(name: string, before: Snapshot, merge?: string) {\n\t\tthis.doc = this.refreshed(this.doc);\n\t\tconst after = this.snapshot();\n\t\tif (before.doc === after.doc) {\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst command: Command = {\n\t\t\tname,\n\t\t\texecute: () => this.restore(after),\n\t\t\tundo: () => this.restore(before),\n\t\t\tredo: () => this.restore(after),\n\t\t\t...(merge ? { tag: merge } : {})\n\t\t};\n\t\tthis.committing = true;\n\t\tif (merge) this.history.executeMerged(command, { key: merge });\n\t\telse this.history.execute(command);\n\t\tthis.committing = false;\n\t}\n\n\t/** Runs `fn` (which updates fields) and records it as one undo step (merged by `merge` key). */\n\tprivate change(name: string, fn: () => void, merge?: string): void {\n\t\tconst before = this.snapshot();\n\t\tfn();\n\t\tthis.commit(name, before, merge);\n\t}\n\n\t/** Ends a merged edit (a slider released): the next change starts a new undo step. */\n\tendMergedEdit(): void {\n\t\tthis.history.endGesture();\n\t}\n\n\tprivate setDoc(nodes: VectorNode[]) {\n\t\tif (nodes !== this.doc.nodes) this.doc = { ...this.doc, nodes };\n\t}\n\n\tprivate replaceNode(id: string, next: VectorNode | null) {\n\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === id ? next : n)));\n\t}\n\n\tprivate freshIds<T extends VectorNode>(node: T): T {\n\t\tconst copy = cloneVectorNode(node);\n\t\tconst used = new Set<string>();\n\t\twalkVectorNodes(this.doc.nodes, (n) => used.add(n.id));\n\t\tconst next = () => {\n\t\t\tlet id = this.createId();\n\t\t\twhile (used.has(id)) id = this.createId();\n\t\t\tused.add(id);\n\t\t\treturn id;\n\t\t};\n\t\tconst assign = (n: VectorNode) => {\n\t\t\tn.id = next();\n\t\t\tif (n.type === 'group') n.children.forEach(assign);\n\t\t};\n\t\tassign(copy);\n\t\treturn copy;\n\t}\n\n\tprivate newId(): string {\n\t\tlet id = this.createId();\n\t\twhile (this.getNode(id)) id = this.createId();\n\t\treturn id;\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Viewport\n\t// -------------------------------------------------------------------------\n\n\tscreenToDocument(p: Vec2): Vec2 {\n\t\tconst v = this.viewport;\n\t\treturn { x: (p.x - v.x) / v.zoom, y: (p.y - v.y) / v.zoom };\n\t}\n\n\tdocumentToScreen(p: Vec2): Vec2 {\n\t\tconst v = this.viewport;\n\t\treturn { x: p.x * v.zoom + v.x, y: p.y * v.zoom + v.y };\n\t}\n\n\tsetViewport(viewport: Partial<VectorViewport>): void {\n\t\tconst next = { ...this.viewport, ...viewport };\n\t\tnext.zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, next.zoom));\n\t\tif (next.x === this.viewport.x && next.y === this.viewport.y && next.zoom === this.viewport.zoom) return;\n\t\tthis.viewport = next;\n\t\tthis.notify();\n\t}\n\n\t/** Zooms by `factor` keeping the document point under `screen` in place. */\n\tzoomAt(screen: Vec2, factor: number): void {\n\t\tconst zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, this.viewport.zoom * factor));\n\t\tconst doc = this.screenToDocument(screen);\n\t\tthis.setViewport({ zoom, x: screen.x - doc.x * zoom, y: screen.y - doc.y * zoom });\n\t}\n\n\tpanBy(dx: number, dy: number): void {\n\t\tthis.setViewport({ x: this.viewport.x + dx, y: this.viewport.y + dy });\n\t}\n\n\t/** Fits the artboard (or `bounds`) into a canvas of `size` screen pixels. */\n\tzoomToFit(size: { width: number; height: number }, options: { padding?: number; bounds?: VectorBounds } = {}): void {\n\t\tconst padding = options.padding ?? 40;\n\t\tconst b = options.bounds ?? { x: 0, y: 0, width: this.doc.width, height: this.doc.height };\n\t\tconst zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, Math.min((size.width - padding * 2) / (b.width || 1), (size.height - padding * 2) / (b.height || 1))));\n\t\tthis.setViewport({ zoom, x: (size.width - b.width * zoom) / 2 - b.x * zoom, y: (size.height - b.height * zoom) / 2 - b.y * zoom });\n\t}\n\n\t/** Zoom at the canvas centre-ish: positive `delta` zooms out (wheel convention). Use with `wheel`. */\n\twheel(screen: Vec2, delta: { x: number; y: number }, mods: VectorModifiers = {}): void {\n\t\tif (mods.mod) this.zoomAt(screen, Math.exp(-delta.y * 0.01));\n\t\telse this.panBy(-(mods.shift ? delta.y : delta.x), -(mods.shift ? 0 : delta.y));\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Settings\n\t// -------------------------------------------------------------------------\n\n\tsetTool(tool: VectorTool): void {\n\t\tif (tool === this.tool) return;\n\t\tthis.finishPath();\n\t\tthis.cancelDrag();\n\t\tthis.tool = tool;\n\t\tthis.hover = null;\n\t\tthis.guides = null;\n\t\tthis.widthPoints = [];\n\t\tthis.widthEdit = null;\n\t\tthis.builderPreview = null;\n\t\tthis.builderHoverKey = null;\n\t\tthis.blendStart = null;\n\t\tif (tool !== 'direct') {\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t}\n\t\tthis.notify();\n\t}\n\n\tsetDefaults(defaults: Partial<VectorDefaults>): void {\n\t\tthis.defaults = { ...this.defaults, ...defaults };\n\t\tthis.notify();\n\t}\n\n\tsetSnap(snap: Partial<VectorSnapOptions>): void {\n\t\tthis.snap = { ...this.snap, ...snap };\n\t\tif (!this.snap.guides || !this.snap.objects) this.guides = null;\n\t\tthis.notify();\n\t}\n\n\t/** Changes how deleting, dragging anchors, the pencil and the eraser behave. */\n\tsetEditing(editing: Partial<VectorPathEditOptions>): void {\n\t\tconst next = { ...this.editing, ...editing };\n\t\tnext.pencilSmoothing = Math.min(1, Math.max(0, Number.isFinite(next.pencilSmoothing) ? next.pencilSmoothing : 0));\n\t\tif (!(next.eraserSize > 0)) next.eraserSize = this.editing.eraserSize;\n\t\tthis.editing = next;\n\t\tthis.notify();\n\t}\n\n\t/** Turns object snapping on or off (the toolbar's snapping toggle). */\n\ttoggleSnapping(on: boolean = !this.snap.objects): void {\n\t\tthis.setSnap({ objects: on });\n\t}\n\n\tprivate gridValue(v: number): number {\n\t\tconst g = this.snap.grid;\n\t\treturn g > 0 ? Math.round(v / g) * g : v;\n\t}\n\n\tprivate snapPoint(p: Vec2): Vec2 {\n\t\treturn { x: this.gridValue(p.x), y: this.gridValue(p.y) };\n\t}\n\n\t/** The pull distance in document units. */\n\tprivate snapTolerance(): number {\n\t\treturn this.snap.threshold / this.viewport.zoom;\n\t}\n\n\tprivate setGuides(guides: VectorSnapGuides | null) {\n\t\tthis.guides = guides && this.snap.guides && (guides.lines.length || guides.labels.length) ? guides : null;\n\t}\n\n\t/**\n\t * Where a drawn or dragged point `p` goes: onto the smart guide `targets` (gathered by\n\t * `gather` when missing), else onto the grid, kept to `leave`'s constraint. Cmd / Ctrl\n\t * bypasses snapping.\n\t */\n\tprivate snapDrawn(p: Vec2, leave: VectorSnapLeave | null, input: VectorModifiers, targets: () => VectorSnapTargets): Vec2 {\n\t\tthis.snappedToObject = false;\n\t\tif (input.mod) {\n\t\t\tthis.setGuides(null);\n\t\t\treturn constrainToLeave(leave, p);\n\t\t}\n\t\tif (this.snap.objects) {\n\t\t\tconst withAngles = leave ? { ...leave, angles: this.snap.angles } : null;\n\t\t\tconst res = targets().snapFrom(p, withAngles, this.snapTolerance());\n\t\t\tif (res.kind) {\n\t\t\t\tthis.snappedToObject = true;\n\t\t\t\tthis.setGuides(res);\n\t\t\t\t// An axis lined up with nothing still goes to the grid.\n\t\t\t\treturn leave?.constrain ? res.point : { x: res.x ? res.point.x : this.gridValue(res.point.x), y: res.y ? res.point.y : this.gridValue(res.point.y) };\n\t\t\t}\n\t\t}\n\t\tthis.setGuides(null);\n\t\treturn constrainToLeave(leave, this.snapPoint(p));\n\t}\n\n\t/** Drawing targets for the pen and shape tools, reused while the document stays the same. */\n\tprivate drawingTargets(exclude: string | null): VectorSnapTargets {\n\t\tconst key = exclude ?? '';\n\t\tconst cached = this.hoverTargets;\n\t\tif (cached && cached.doc === this.doc && cached.exclude === key) return cached.targets;\n\t\tconst targets = VectorSnapTargets.forDrawing(this.doc, exclude ? [exclude] : []);\n\t\tthis.hoverTargets = { doc: this.doc, exclude: key, targets };\n\t\treturn targets;\n\t}\n\n\t/** Where the pointer at `p` would place a point with the pen or a shape tool (and the guides showing why). */\n\tprivate hoverSnap(p: Vec2, input: VectorModifiers): Vec2 {\n\t\tif (this.tool === 'pen') {\n\t\t\tconst sp = this.pen ? (this.getNode(this.pen.nodeId) as VectorPathNode | undefined)?.subpaths[this.pen.subpath] : undefined;\n\t\t\tif (this.pen && sp && this.hover?.anchor) {\n\t\t\t\tthis.setGuides(null);\n\t\t\t\treturn p;\n\t\t\t}\n\t\t\tconst last = this.pen && sp ? getAnchorWorld(this.doc, { nodeId: this.pen.nodeId, subpath: this.pen.subpath, index: sp.anchors.length - 1 }) : undefined;\n\t\t\tconst leave = last ? { at: { x: last.x, y: last.y }, constrain: input.shift ? ('angle' as const) : null } : null;\n\t\t\treturn this.snapDrawn(p, leave, input, () => this.drawingTargets(this.pen?.nodeId ?? null));\n\t\t}\n\t\tif (SHAPE_TOOLS.has(this.tool)) return this.snapDrawn(p, null, input, () => this.drawingTargets(null));\n\t\tthis.setGuides(null);\n\t\treturn p;\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Document\n\t// -------------------------------------------------------------------------\n\n\t/** Replaces the document. Undoable unless `{ history: false }` (then history is cleared). */\n\tsetDocument(doc: VectorDocument, options: { history?: boolean; label?: string } = {}): void {\n\t\tthis.cancelDrag();\n\t\tconst next = cloneVectorDocument(doc);\n\t\tif (options.history === false) {\n\t\t\tthis.doc = next;\n\t\t\tthis.selection = [];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t\tthis.pen = null;\n\t\t\tthis.history.clear();\n\t\t\tthis.notify(true);\n\t\t\treturn;\n\t\t}\n\t\tthis.change(options.label ?? 'Replace Document', () => {\n\t\t\tthis.doc = next;\n\t\t\tthis.selection = [];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t\tthis.pen = null;\n\t\t});\n\t}\n\n\t/** Changes the artboard size or background. */\n\tsetArtboard(patch: Partial<Pick<VectorDocument, 'width' | 'height' | 'background'>>): void {\n\t\tthis.change('Change Artboard', () => {\n\t\t\tthis.doc = { ...this.doc, ...patch };\n\t\t});\n\t}\n\n\t/** Replaces the document with parsed SVG markup (one undo step). */\n\tloadSvg(svg: string, options: { history?: boolean } = {}): VectorActionResult {\n\t\ttry {\n\t\t\tconst doc = parseVectorSvg(svg, { createId: () => this.createId() });\n\t\t\tthis.setDocument(doc, { history: options.history, label: 'Import SVG' });\n\t\t\treturn { ok: true };\n\t\t} catch (error) {\n\t\t\treturn { ok: false, message: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t}\n\n\t/** Adds the nodes of SVG markup on top of the document, selected. */\n\timportSvg(svg: string): VectorActionResult {\n\t\ttry {\n\t\t\tconst parsed = parseVectorSvg(svg);\n\t\t\tconst nodes = parsed.nodes.map((n) => this.freshIds(n));\n\t\t\tif (!nodes.length) return { ok: false, message: 'The SVG has no shapes the editor can read.' };\n\t\t\tthis.change('Import SVG', () => {\n\t\t\t\tthis.setDoc([...this.doc.nodes, ...nodes]);\n\t\t\t\tthis.selection = nodes.map((n) => n.id);\n\t\t\t\tthis.anchors = [];\n\t\t\t});\n\t\t\treturn { ok: true };\n\t\t} catch (error) {\n\t\t\treturn { ok: false, message: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t}\n\n\ttoSvg(options?: VectorSvgExportOptions): string {\n\t\treturn vectorDocumentToSvg(this.doc, options);\n\t}\n\n\ttoJSON(): VectorDocument {\n\t\treturn cloneVectorDocument(this.doc);\n\t}\n\n\t/** Adds a node (ids are kept unless taken). Returns its id. */\n\taddNode(node: VectorNode, options: AddVectorNodeOptions = {}): string {\n\t\tconst taken = (() => {\n\t\t\tlet clash = false;\n\t\t\twalkVectorNodes([node], (n) => {\n\t\t\t\tif (this.getNode(n.id)) clash = true;\n\t\t\t});\n\t\t\treturn clash;\n\t\t})();\n\t\tconst next = taken ? this.freshIds(node) : cloneVectorNode(node);\n\t\tthis.change(options.label ?? `Add ${next.type === 'group' ? 'Group' : (next.name ?? 'Path')}`, () => {\n\t\t\tconst insert = (list: VectorNode[]) => {\n\t\t\t\tconst copy = list.slice();\n\t\t\t\tcopy.splice(options.index ?? copy.length, 0, next);\n\t\t\t\treturn copy;\n\t\t\t};\n\t\t\tif (options.parentId) {\n\t\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === options.parentId && n.type === 'group' ? { ...n, children: insert(n.children) } : n)));\n\t\t\t} else this.setDoc(insert(this.doc.nodes));\n\t\t\tif (options.select !== false) {\n\t\t\t\tthis.selection = [next.id];\n\t\t\t\tthis.anchors = [];\n\t\t\t}\n\t\t});\n\t\treturn next.id;\n\t}\n\n\t/** Builds a path node for a shape with the current defaults (not added). */\n\tcreateShapeNode(input: VectorShapeInput): VectorPathNode {\n\t\tconst b = { x: input.x, y: input.y, width: input.width, height: input.height };\n\t\tconst d = this.defaults;\n\t\tlet subpath: VectorSubpath;\n\t\tswitch (input.kind) {\n\t\t\tcase 'rect':\n\t\t\t\tsubpath = rectSubpath(b);\n\t\t\t\tbreak;\n\t\t\tcase 'ellipse':\n\t\t\t\tsubpath = ellipseSubpath(b);\n\t\t\t\tbreak;\n\t\t\tcase 'polygon':\n\t\t\t\tsubpath = polygonSubpath(b, input.sides ?? d.polygonSides);\n\t\t\t\tbreak;\n\t\t\tcase 'star':\n\t\t\t\tsubpath = starSubpath(b, input.points ?? d.starPoints, input.innerRatio ?? d.starInnerRatio);\n\t\t\t\tbreak;\n\t\t\tcase 'line':\n\t\t\t\tsubpath = lineSubpath({ x: b.x, y: b.y }, { x: b.x + b.width, y: b.y + b.height });\n\t\t\t\tbreak;\n\t\t}\n\t\tconst radius = input.cornerRadius ?? d.cornerRadius;\n\t\tconst kind = input.cornerKind ?? d.cornerKind;\n\t\t// Live corners: the shape keeps its sharp anchors, so the radius stays editable.\n\t\tif ((input.kind === 'rect' || input.kind === 'polygon' || input.kind === 'star') && radius > 0) subpath = setSubpathCorners(subpath, radius, kind);\n\t\tconst names = { rect: 'Rectangle', ellipse: 'Ellipse', polygon: 'Polygon', star: 'Star', line: 'Line' };\n\t\tconst style = this.styleFor(input.kind === 'line');\n\t\tconst own: VectorStyle = {};\n\t\tfor (const key of VECTOR_STYLE_KEYS) {\n\t\t\tif (input[key] !== undefined) (own as any)[key] = input[key];\n\t\t}\n\t\treturn { id: this.newId(), type: 'path', name: input.name ?? names[input.kind], ...style, ...own, subpaths: [subpath] };\n\t}\n\n\t/** Adds a shape drawn with the current defaults. Returns its id. */\n\taddShape(input: VectorShapeInput, options: AddVectorNodeOptions = {}): string {\n\t\tconst node = this.createShapeNode(input);\n\t\treturn this.addNode(node, { label: `Add ${node.name}`, ...options });\n\t}\n\n\t/** Adds a path from SVG path data. Returns its id. */\n\taddPath(d: string, style: VectorStyle & { name?: string } = {}, options: AddVectorNodeOptions = {}): string {\n\t\tconst subpaths = bezierToSubpaths(d);\n\t\tconst node: VectorPathNode = { id: this.newId(), type: 'path', name: style.name ?? 'Path', ...this.styleFor(false), ...style, subpaths };\n\t\treturn this.addNode(node, { label: 'Add Path', ...options });\n\t}\n\n\tprivate styleFor(open: boolean): VectorStyle {\n\t\tconst d = this.defaults;\n\t\tconst out: VectorStyle = { fill: open ? null : (d.fill ?? null), stroke: d.stroke ?? null, strokeWidth: d.strokeWidth ?? 1 };\n\t\tif (d.strokeLinecap) out.strokeLinecap = d.strokeLinecap;\n\t\tif (d.strokeLinejoin) out.strokeLinejoin = d.strokeLinejoin;\n\t\tif (d.strokeDasharray) out.strokeDasharray = d.strokeDasharray;\n\t\tif (d.fillRule) out.fillRule = d.fillRule;\n\t\tif (d.strokeAlign && d.strokeAlign !== 'center') out.strokeAlign = d.strokeAlign;\n\t\tfor (const key of ['startArrow', 'endArrow', 'arrowAlign', 'startArrowScale', 'endArrowScale'] as const) if (d[key]) (out as any)[key] = d[key];\n\t\tif (d.strokeWidthProfile?.length) out.strokeWidthProfile = d.strokeWidthProfile.map((p) => ({ ...p }));\n\t\treturn out;\n\t}\n\n\t/** Merges `patch` into a node (paths take style and geometry; groups name / visibility / opacity). */\n\tupdateNode(id: string, patch: Partial<VectorPathNode> | Partial<VectorGroupNode>, options: { label?: string } = {}): VectorActionResult {\n\t\tconst node = this.getNode(id);\n\t\tif (!node) return { ok: false, message: `There is no node \"${id}\".` };\n\t\tconst { id: _id, type: _type, ...rest } = patch as any;\n\t\tthis.change(options.label ?? 'Edit Node', () => this.replaceNode(id, { ...node, ...rest } as VectorNode));\n\t\treturn { ok: true };\n\t}\n\n\t/** Applies style (and opacity) to the selected (or given) nodes; groups pass it to every path inside. */\n\tsetStyle(style: VectorStyle & { opacity?: number }, ids: readonly string[] = this.selection, options: { label?: string } = {}): VectorActionResult {\n\t\tconst targets = new Set<string>();\n\t\tfor (const id of ids) {\n\t\t\tconst node = this.getNode(id);\n\t\t\tif (!node) return { ok: false, message: `There is no node \"${id}\".` };\n\t\t\tfor (const p of collectPathNodes(node)) targets.add(p.id);\n\t\t}\n\t\tif (!targets.size) return { ok: false, message: 'Nothing is selected.' };\n\t\tthis.change(options.label ?? 'Change Style', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (targets.has(n.id) ? withStyle(n as VectorPathNode, style) : n)));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * Sets the variable-width profile of the selected (or given) paths: a preset id\n\t * (`uniform` removes the profile), points, or a parametric taper / wave. Returns an error for\n\t * an unknown preset.\n\t */\n\tsetWidthProfile(\n\t\tprofile: VectorWidthPreset | readonly VectorWidthPoint[] | { taper?: VectorTaperOptions; wave?: VectorWaveOptions } | null,\n\t\toptions: { ids?: readonly string[] } = {}\n\t): VectorActionResult {\n\t\tlet points: VectorWidthPoint[];\n\t\tif (profile === null) points = [];\n\t\telse if (typeof profile === 'string') {\n\t\t\tconst preset = widthProfilePreset(profile);\n\t\t\tif (!preset) return { ok: false, message: `There is no width profile \"${profile}\".` };\n\t\t\tpoints = preset;\n\t\t} else if (Array.isArray(profile)) points = sanitizeWidthProfile(profile);\n\t\telse {\n\t\t\tconst p = profile as { taper?: VectorTaperOptions; wave?: VectorWaveOptions };\n\t\t\tpoints = p.taper || p.wave ? generateWidthProfile(p) : [];\n\t\t}\n\t\tthis.widthPoints = [];\n\t\tthis.widthEdit = null;\n\t\treturn this.setStyle({ strokeWidthProfile: points.length ? points : null }, options.ids ?? this.selection, { label: points.length ? 'Width Profile' : 'Uniform Width' });\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Gradients\n\t// -------------------------------------------------------------------------\n\n\t/** Which paint (fill or stroke) the gradient tool and editor work on. */\n\tsetGradientTarget(target: VectorGradientTarget): void {\n\t\tif (target === this.gradientEdit.target) return;\n\t\tthis.gradientEdit = { target, stop: null };\n\t\tthis.notify();\n\t}\n\n\t/** Selects a stop of the gradient being edited (index into its sorted stops), or none. */\n\tselectGradientStop(index: number | null): void {\n\t\tif (index === this.gradientEdit.stop) return;\n\t\tthis.gradientEdit = { ...this.gradientEdit, stop: index };\n\t\tthis.notify();\n\t}\n\n\t/** The gradient tool's annotator for the selection (screen pixels), or null when its paint is not a gradient. */\n\tgetGradientAnnotator(): VectorGradientAnnotator | null {\n\t\treturn getVectorGradientAnnotator(this.doc, this.selection, this.viewport, this.gradientEdit.target);\n\t}\n\n\t/** Unlocked paths of `ids` (default: the selection). */\n\tprivate gradientPaths(ids: readonly string[] = this.selection): VectorPathNode[] {\n\t\tconst out = new Map<string, VectorPathNode>();\n\t\tfor (const id of ids) {\n\t\t\tconst node = this.getNode(id);\n\t\t\tif (node && !node.locked) for (const p of collectPathNodes(node)) if (!p.locked) out.set(p.id, p);\n\t\t}\n\t\treturn [...out.values()];\n\t}\n\n\t/**\n\t * Edits the gradient of the `target` paint (default: the editor's) of the selected (or given)\n\t * paths with `patch`: a colour or no paint becomes a gradient (`patch.type`, default linear,\n\t * from the colour to white). Consecutive edits with the same `merge` key are one undo step.\n\t */\n\tsetGradient(patch: VectorGradientPatch, options: { ids?: readonly string[]; target?: VectorGradientTarget; label?: string; merge?: string } = {}): VectorActionResult {\n\t\tconst target = options.target ?? this.gradientEdit.target;\n\t\tconst paths = this.gradientPaths(options.ids ?? this.selection);\n\t\tif (!paths.length) return { ok: false, message: 'Nothing is selected.' };\n\t\tconst next = new Map<string, VectorPathNode>();\n\t\tfor (const path of paths) {\n\t\t\tconst paint = path[target];\n\t\t\tconst base = isVectorGradient(paint) ? paint : defaultVectorGradient(patch.type ?? 'linear', paint);\n\t\t\tconst g = applyVectorGradientPatch(base, patch);\n\t\t\tif (!g) return { ok: false, message: 'That is not a valid gradient: it needs at least one stop with an offset and a colour.' };\n\t\t\tnext.set(path.id, { ...path, [target]: g });\n\t\t}\n\t\tthis.change(options.label ?? 'Gradient', () => this.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n)), options.merge);\n\t\tconst stops = (next.values().next().value?.[target] as { stops: unknown[] } | undefined)?.stops.length ?? 0;\n\t\tif (this.gradientEdit.stop !== null && this.gradientEdit.stop >= stops) this.gradientEdit = { ...this.gradientEdit, stop: null };\n\t\treturn { ok: true };\n\t}\n\n\t/** Applies `fn` to every target gradient of the selection (paths whose paint is not a gradient are skipped). */\n\tprivate editGradients(label: string, fn: (stops: VectorGradient['stops']) => VectorGradient['stops'] | null, merge?: string): VectorActionResult {\n\t\tconst target = this.gradientEdit.target;\n\t\tconst paths = this.gradientPaths().filter((p) => isVectorGradient(p[target]));\n\t\tif (!paths.length) return { ok: false, message: 'The selection has no gradient.' };\n\t\tconst next = new Map<string, VectorPathNode>();\n\t\tfor (const path of paths) {\n\t\t\tconst g = path[target] as VectorGradient;\n\t\t\tconst stops = fn(sortedVectorGradientStops(g));\n\t\t\tif (!stops) return { ok: false, message: 'A gradient keeps at least two stops.' };\n\t\t\tnext.set(path.id, { ...path, [target]: { ...g, stops } });\n\t\t}\n\t\tthis.change(label, () => this.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n)), merge);\n\t\treturn { ok: true };\n\t}\n\n\t/** Changes stop `index` (colour, opacity, offset or midpoint) of the selection's gradients; selects it. */\n\tsetGradientStop(index: number, patch: { color?: string; opacity?: number; offset?: number; midpoint?: number }, options: { merge?: string } = {}): VectorActionResult {\n\t\tlet moved = index;\n\t\tconst result = this.editGradients(\n\t\t\t'Gradient Stop',\n\t\t\t(stops) => {\n\t\t\t\tif (!stops[index]) return stops;\n\t\t\t\tlet next = stops.map((s, i) => {\n\t\t\t\t\tif (i !== index) return s;\n\t\t\t\t\tconst out = { ...s };\n\t\t\t\t\tif (patch.color !== undefined) out.color = patch.color;\n\t\t\t\t\tif (patch.opacity !== undefined && Number.isFinite(patch.opacity)) {\n\t\t\t\t\t\tconst o = Math.min(1, Math.max(0, patch.opacity));\n\t\t\t\t\t\tif (o < 1) out.opacity = o;\n\t\t\t\t\t\telse delete out.opacity;\n\t\t\t\t\t}\n\t\t\t\t\tif (patch.midpoint !== undefined && Number.isFinite(patch.midpoint)) {\n\t\t\t\t\t\tconst m = Math.min(0.95, Math.max(0.05, patch.midpoint));\n\t\t\t\t\t\tif (Math.abs(m - 0.5) > 1e-3) out.midpoint = m;\n\t\t\t\t\t\telse delete out.midpoint;\n\t\t\t\t\t}\n\t\t\t\t\treturn out;\n\t\t\t\t});\n\t\t\t\tif (patch.offset !== undefined && Number.isFinite(patch.offset)) ({ stops: next, index: moved } = moveVectorGradientStop(next, index, patch.offset));\n\t\t\t\treturn next;\n\t\t\t},\n\t\t\toptions.merge\n\t\t);\n\t\tif (result.ok) this.selectGradientStop(moved);\n\t\treturn result;\n\t}\n\n\t/** Adds a stop at `offset` (painting what the gradient paints there) to the selection's gradients and selects it. */\n\taddGradientStop(offset: number): VectorActionResult {\n\t\tlet index = 0;\n\t\tconst target = this.gradientEdit.target;\n\t\tconst result = this.editGradients('Add Gradient Stop', (stops) => {\n\t\t\tconst path = this.gradientPaths().find((p) => isVectorGradient(p[target]));\n\t\t\tconst r = insertVectorGradientStop({ ...(path![target] as VectorGradient), stops }, offset);\n\t\t\tindex = r.index;\n\t\t\treturn r.stops;\n\t\t});\n\t\tif (result.ok) this.selectGradientStop(index);\n\t\treturn result;\n\t}\n\n\t/** Removes stop `index` of the selection's gradients (never below two stops). */\n\tremoveGradientStop(index: number): VectorActionResult {\n\t\tconst result = this.editGradients('Delete Gradient Stop', (stops) => removeVectorGradientStop(stops, index));\n\t\tif (result.ok) this.selectGradientStop(null);\n\t\treturn result;\n\t}\n\n\t/** Reverses the selection's gradients (stops and midpoints). */\n\treverseGradient(): VectorActionResult {\n\t\treturn this.setGradient({ reverse: true }, { label: 'Reverse Gradient' });\n\t}\n\n\t/** `doc` with path `id`'s `target` paint replaced. */\n\tprivate withGradient(doc: VectorDocument, id: string, target: VectorGradientTarget, g: VectorGradient): VectorDocument {\n\t\treturn { ...doc, nodes: mapTree(doc.nodes, (n) => (n.id === id ? ({ ...n, [target]: g } as VectorNode) : n)) };\n\t}\n\n\tprivate downGradient(screen: Vec2, p: Vec2, input: VectorPointerInput) {\n\t\tconst before = this.snapshot();\n\t\tconst a = this.getGradientAnnotator();\n\t\tlet part = a ? hitVectorGradientAnnotator(a, screen) : null;\n\t\tif (a && part) {\n\t\t\t// Alt on a radial gradient's centre grabs its focal point (they sit together until moved).\n\t\t\tif (part.kind === 'handle' && part.handle === 'start' && input.alt && a.gradient.type === 'radial') part = { kind: 'handle', handle: 'focal' };\n\t\t\tif (part.kind === 'stop') this.gradientEdit = { ...this.gradientEdit, stop: part.index };\n\t\t\tthis.drag = { kind: 'gradient', part, nodeId: a.nodeId, targets: [a.nodeId], annotator: a, start: screen, startDoc: p, before, base: this.doc, moved: false, stop: part.kind === 'stop' ? part.index : null };\n\t\t\tthis.interaction = { type: 'gradient', part: part.kind, ...(part.kind === 'handle' ? { handle: part.handle } : {}) };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\t// Elsewhere a drag draws a new vector over the path under the pointer (selecting it) or the selection.\n\t\tconst hit = this.hitPath(p);\n\t\tif (hit && !this.gradientPaths().some((n) => n.id === hit.id)) {\n\t\t\tthis.selection = [hit.id];\n\t\t\tthis.gradientEdit = { ...this.gradientEdit, stop: null };\n\t\t}\n\t\tconst targets = this.gradientPaths().map((n) => n.id);\n\t\tif (!targets.length) {\n\t\t\tif (this.selection.length) this.clearSelection();\n\t\t\treturn;\n\t\t}\n\t\tthis.drag = { kind: 'gradient', part: null, nodeId: null, targets, annotator: null, start: screen, startDoc: p, before, base: this.doc, moved: false, stop: null };\n\t\tthis.interaction = { type: 'gradient', part: 'create' };\n\t\tthis.notify();\n\t}\n\n\tprivate moveGradient(drag: Extract<Drag, { kind: 'gradient' }>, screen: Vec2, p: Vec2, input: VectorPointerInput) {\n\t\tif (!drag.moved && Math.hypot(screen.x - drag.start.x, screen.y - drag.start.y) < 3) return;\n\t\tdrag.moved = true;\n\t\tconst target = this.gradientEdit.target;\n\t\tconst base = drag.base;\n\t\tconst constrained = (from: Vec2, to: Vec2) => {\n\t\t\tif (!input.shift) return to;\n\t\t\tconst ang = Math.round(Math.atan2(to.y - from.y, to.x - from.x) / (Math.PI / 4)) * (Math.PI / 4);\n\t\t\tconst len = Math.hypot(to.x - from.x, to.y - from.y);\n\t\t\treturn { x: from.x + Math.cos(ang) * len, y: from.y + Math.sin(ang) * len };\n\t\t};\n\t\tif (!drag.part) {\n\t\t\tconst end = constrained(drag.startDoc, p);\n\t\t\tlet doc = base;\n\t\t\tfor (const id of drag.targets) {\n\t\t\t\tconst node = findVectorNode(base.nodes, id) as VectorPathNode | undefined;\n\t\t\t\tif (!node) continue;\n\t\t\t\tconst paint = node[target];\n\t\t\t\tconst g = isVectorGradient(paint) ? paint : defaultVectorGradient('linear', paint);\n\t\t\t\tdoc = this.withGradient(doc, id, target, setVectorGradientVector(g, vectorPathLocalBounds(node), this.toLocal(id, drag.startDoc, base), this.toLocal(id, end, base)));\n\t\t\t}\n\t\t\tthis.doc = doc;\n\t\t\tthis.notify(true);\n\t\t\treturn;\n\t\t}\n\t\tconst id = drag.nodeId!;\n\t\tconst node = findVectorNode(base.nodes, id) as VectorPathNode;\n\t\tconst g = node[target] as VectorGradient;\n\t\tconst bounds = vectorPathLocalBounds(node);\n\t\tconst local = (q: Vec2) => this.toLocal(id, q, base);\n\t\tconst part = drag.part;\n\t\tlet next: VectorGradient = g;\n\t\tif (part.kind === 'handle') {\n\t\t\tlet to = p;\n\t\t\t// Shift keeps the vector at 45° steps from the start.\n\t\t\tif (part.handle === 'end' || part.handle === 'radius') to = constrained(this.screenToDocument(drag.annotator!.start), p);\n\t\t\tnext = moveVectorGradientHandle(g, bounds, part.handle, local(to), local(drag.startDoc));\n\t\t} else if (part.kind === 'bar') {\n\t\t\tnext = moveVectorGradientHandle(g, bounds, 'start', local(p), local(drag.startDoc));\n\t\t} else {\n\t\t\tconst a = drag.annotator!;\n\t\t\tconst pos = vectorGradientBarPosition(a, screen);\n\t\t\tconst stops = sortedVectorGradientStops(g);\n\t\t\tif (part.kind === 'stop') {\n\t\t\t\tconst off = Math.abs(pos.across - VECTOR_GRADIENT_STOP_GAP) > VECTOR_GRADIENT_OFF_BAR;\n\t\t\t\tconst removed = off ? removeVectorGradientStop(stops, part.index) : null;\n\t\t\t\tif (removed) {\n\t\t\t\t\tnext = { ...g, stops: removed };\n\t\t\t\t\tdrag.stop = null;\n\t\t\t\t} else {\n\t\t\t\t\tconst r = moveVectorGradientStop(stops, part.index, Math.min(1, Math.max(0, pos.offset)));\n\t\t\t\t\tnext = { ...g, stops: r.stops };\n\t\t\t\t\tdrag.stop = r.index;\n\t\t\t\t}\n\t\t\t\tthis.interaction = { type: 'gradient', part: 'stop', ...(removed ? { removing: true } : {}) };\n\t\t\t\tthis.gradientEdit = { ...this.gradientEdit, stop: drag.stop };\n\t\t\t} else {\n\t\t\t\tconst m = vectorGradientMidpointFromPosition(stops, part.index, pos.offset);\n\t\t\t\tif (m !== null) next = { ...g, stops: stops.map((s, i) => (i !== part.index ? s : Math.abs(m - 0.5) < 1e-3 ? (({ midpoint: _m, ...rest }) => rest)(s) : { ...s, midpoint: m })) };\n\t\t\t}\n\t\t}\n\t\tthis.doc = this.withGradient(base, id, target, next);\n\t\tthis.notify(true);\n\t}\n\n\tprivate upGradient(drag: Extract<Drag, { kind: 'gradient' }>) {\n\t\tconst part = drag.part;\n\t\tif (!drag.moved) {\n\t\t\tif (part?.kind === 'bar') {\n\t\t\t\tthis.addGradientStop(part.offset);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst label = !part\n\t\t\t? 'Gradient'\n\t\t\t: part.kind === 'stop'\n\t\t\t\t? drag.stop === null\n\t\t\t\t\t? 'Delete Gradient Stop'\n\t\t\t\t\t: 'Move Gradient Stop'\n\t\t\t\t: part.kind === 'midpoint'\n\t\t\t\t\t? 'Gradient Midpoint'\n\t\t\t\t\t: part.kind === 'bar' || (part.kind === 'handle' && part.handle === 'start')\n\t\t\t\t\t\t? 'Move Gradient'\n\t\t\t\t\t\t: 'Gradient Handle';\n\t\treturn this.commit(label, drag.before);\n\t}\n\n\t/** Removes one width point of a path's profile. */\n\tremoveWidthPoint(ref: Pick<VectorWidthPointRef, 'nodeId' | 'index'>): VectorActionResult {\n\t\treturn this.removeWidthPoints([ref]);\n\t}\n\n\t/** Removes width points (default: the selected ones — the width tool's Delete), as one undo step. */\n\tremoveWidthPoints(refs: readonly Pick<VectorWidthPointRef, 'nodeId' | 'index'>[] = this.widthPoints): VectorActionResult {\n\t\tconst byNode = new Map<string, number[]>();\n\t\tfor (const r of refs) {\n\t\t\tconst node = this.getNode(r.nodeId);\n\t\t\tif (node?.type !== 'path' || !node.strokeWidthProfile?.[r.index]) continue;\n\t\t\tbyNode.set(r.nodeId, [...(byNode.get(r.nodeId) ?? []), r.index]);\n\t\t}\n\t\tif (!byNode.size) return { ok: false, message: 'There is no such width point.' };\n\t\tconst count = [...byNode.values()].reduce((n, l) => n + new Set(l).size, 0);\n\t\tthis.widthPoints = [];\n\t\tthis.widthEdit = null;\n\t\tthis.change(count > 1 ? 'Delete Width Points' : 'Delete Width Point', () => {\n\t\t\tfor (const [id, indices] of byNode) {\n\t\t\t\tconst node = this.getNode(id) as VectorPathNode;\n\t\t\t\tconst rest = removeWidthPoints(sanitizeWidthProfile(node.strokeWidthProfile), indices);\n\t\t\t\tthis.replaceNode(id, withStyle(node, { strokeWidthProfile: rest.length ? rest : null }));\n\t\t\t}\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Selects width points (width tool); points on other paths than the first are dropped. */\n\tselectWidthPoints(refs: readonly VectorWidthPointRef[]): void {\n\t\tconst first = refs[0]?.nodeId;\n\t\tconst next: VectorWidthPointRef[] = [];\n\t\tfor (const r of refs) {\n\t\t\tif (r.nodeId !== first || next.some((q) => q.index === r.index)) continue;\n\t\t\tconst n = this.getNode(r.nodeId);\n\t\t\tif (n?.type === 'path' && n.strokeWidthProfile?.[r.index]) next.push({ nodeId: r.nodeId, index: r.index, subpath: r.subpath ?? 0 });\n\t\t}\n\t\tthis.widthPoints = next;\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Sets one width point numerically: `index` names an existing point of path `nodeId`, null\n\t * (or left out) adds one at `values.t`. `left` / `right` are side widths in document units\n\t * (how far the stroke reaches on each side of the path), `t` the position along each subpath\n\t * (0–1). A point moved onto another one's `t` makes a step there. Selects the point and\n\t * refreshes an open width-point editor; one undo step. Ported from ArtCraft vectorcraft's\n\t * Width Point Edit dialog (`crates/tools/src/distort/width.rs`, MIT OR Apache-2.0).\n\t */\n\tsetWidthPoint(target: { nodeId: string; index?: number | null }, values: { t?: number; left?: number; right?: number }): VectorActionResult {\n\t\tconst node = this.getNode(target.nodeId);\n\t\tif (node?.type !== 'path') return { ok: false, message: 'There is no such path.' };\n\t\tconst g = vectorStrokeWidthGeometry(node);\n\t\tif (!g || !(g.unit > 0)) return { ok: false, message: 'The path has no visible stroke.' };\n\t\tconst num = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);\n\t\tconst t = num(values.t);\n\t\tconst left = num(values.left);\n\t\tconst right = num(values.right);\n\t\tconst scale = (w: number | undefined) => (w === undefined ? undefined : Math.max(0, w) / g.unit);\n\t\tconst profile = sanitizeWidthProfile(node.strokeWidthProfile);\n\t\tlet points: VectorWidthPoint[];\n\t\tlet index: number;\n\t\tconst existing = target.index ?? null;\n\t\tif (existing === null) {\n\t\t\tif (t === undefined) return { ok: false, message: 'A new width point needs a position t.' };\n\t\t\tif (profile.length >= VECTOR_WIDTH_PROFILE_MAX_POINTS) return { ok: false, message: 'The profile has too many width points.' };\n\t\t\t({ points, index } = insertWidthPoint(profile.length ? profile : null, t, { left: scale(left), right: scale(right) }, { step: true }));\n\t\t} else {\n\t\t\tconst p = profile[existing];\n\t\t\tif (!p) return { ok: false, message: 'There is no such width point.' };\n\t\t\tconst sides = { left: scale(left) ?? p.left, right: scale(right) ?? p.right };\n\t\t\tconst tt = t === undefined ? p.t : Math.min(Math.max(t, 0), 1);\n\t\t\tif (Math.abs(tt - p.t) <= VECTOR_WIDTH_SAME_T) {\n\t\t\t\tpoints = setWidthPoint(profile, existing, sides);\n\t\t\t\tindex = existing;\n\t\t\t} else {\n\t\t\t\t// Free to pass its neighbours: take it out and put it back at its new place.\n\t\t\t\tconst rest = removeWidthPoints(profile, [existing]);\n\t\t\t\tif (rest.length) ({ points, index } = insertWidthPoint(rest, tt, sides, { step: true }));\n\t\t\t\telse {\n\t\t\t\t\tpoints = [{ t: tt, ...sides }];\n\t\t\t\t\tindex = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst subpath = this.widthEdit?.nodeId === node.id ? this.widthEdit.subpath : this.widthPoints.find((r) => r.nodeId === node.id)?.subpath ?? 0;\n\t\tconst edit = this.widthEdit;\n\t\tthis.change('Width Point', () => this.replaceNode(node.id, withStyle(node, { strokeWidthProfile: points })));\n\t\tthis.widthPoints = [{ nodeId: node.id, index, subpath }];\n\t\tif (edit?.nodeId === node.id && (edit.index === existing || existing === null)) this.widthEdit = this.widthEditFor(node.id, subpath, index, null);\n\t\tthis.notify();\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * Opens the width-point editor (what a double-click with the width tool does) on a width\n\t * point, or on a spot of a stroke (`t` without `index`: applying it adds a point there).\n\t * Returns false when the path has no visible stroke.\n\t */\n\topenWidthPointEdit(target: { nodeId: string; subpath?: number; index?: number | null; t?: number }): boolean {\n\t\tconst edit = this.widthEditFor(target.nodeId, target.subpath ?? 0, target.index ?? null, target.t ?? null);\n\t\tif (!edit) return false;\n\t\tthis.widthEdit = edit;\n\t\tthis.widthPoints = edit.index === null ? [] : [{ nodeId: edit.nodeId, index: edit.index, subpath: edit.subpath }];\n\t\tthis.notify();\n\t\treturn true;\n\t}\n\n\t/** Closes the width-point editor without changing anything. */\n\tcloseWidthPointEdit(): void {\n\t\tif (!this.widthEdit) return;\n\t\tthis.widthEdit = null;\n\t\tthis.notify();\n\t}\n\n\t/** Applies `values` to the width point being edited (see `setWidthPoint`) and closes the editor. */\n\tcommitWidthPointEdit(values: { t?: number; left?: number; right?: number }): VectorActionResult {\n\t\tconst edit = this.widthEdit;\n\t\tif (!edit) return { ok: false, message: 'No width point is being edited.' };\n\t\tconst r = this.setWidthPoint({ nodeId: edit.nodeId, index: edit.index }, edit.index === null ? { t: edit.t, ...values } : values);\n\t\tif (r.ok) this.closeWidthPointEdit();\n\t\treturn r;\n\t}\n\n\t/** The editor's view of point `index` (or of the stroke at `t` when index is null) of `nodeId`. */\n\tprivate widthEditFor(nodeId: string, subpath: number, index: number | null, t: number | null): VectorWidthPointEdit | null {\n\t\tconst node = this.getNode(nodeId);\n\t\tif (node?.type !== 'path') return null;\n\t\tconst g = vectorStrokeWidthGeometry(node);\n\t\tif (!g) return null;\n\t\tconst profile = sanitizeWidthProfile(node.strokeWidthProfile);\n\t\tconst p = index === null ? null : profile[index];\n\t\tif (index !== null && !p) return null;\n\t\tconst tt = p ? p.t : Math.min(Math.max(t ?? 0, 0), 1);\n\t\tconst sides = p ?? widthProfileAt(profile.length ? profile : null, tt);\n\t\tconst sp = Math.min(Math.max(0, subpath), Math.max(0, node.subpaths.length - 1));\n\t\tconst at = strokePositionPoint(g.line, sp, tt);\n\t\tconst round = (v: number) => Math.round(v * 1e6) / 1e6;\n\t\tconst q = at ? applyMatrix(this.worldOf(nodeId), { x: at.point[0], y: at.point[1] }) : { x: 0, y: 0 };\n\t\tconst point = { x: round(q.x), y: round(q.y) };\n\t\treturn { nodeId, subpath: sp, index, t: round(tt), left: round(sides.left * g.unit), right: round(sides.right * g.unit), point };\n\t}\n\n\t/**\n\t * Sets the live corner radius (and optionally the kind) of anchors: `refs`, else the selected\n\t * anchors (direct tool), else every anchor of the `ids` / selected paths. Radius 0 makes the\n\t * corners sharp again. Corners shrink to fit when drawn, so any radius is accepted.\n\t */\n\tsetCornerRadius(radius: number, options: { kind?: VectorCornerKind; refs?: readonly VectorAnchorRef[]; ids?: readonly string[] } = {}): VectorActionResult {\n\t\tif (!Number.isFinite(radius) || radius < 0) return { ok: false, message: 'The radius must be a number of at least 0.' };\n\t\tconst refs = this.cornerTargets(options);\n\t\tif (!refs.length) return { ok: false, message: 'Nothing is selected.' };\n\t\tthis.change(radius > 0 ? 'Corner Radius' : 'Sharpen Corners', () => {\n\t\t\tthis.doc = this.updateAnchors(this.doc, refs, (a) => setAnchorCorner(a, radius, options.kind));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Sets the live corner kind of the same anchors as `setCornerRadius`, keeping their radii. */\n\tsetCornerKind(kind: VectorCornerKind, options: { refs?: readonly VectorAnchorRef[]; ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst refs = this.cornerTargets(options);\n\t\tif (!refs.length) return { ok: false, message: 'Nothing is selected.' };\n\t\tthis.change('Corner Kind', () => {\n\t\t\tthis.doc = this.updateAnchors(this.doc, refs, (a) => setAnchorCorner(a, a.corner?.radius ?? 0, kind));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Anchors a corner change applies to (see `setCornerRadius`). */\n\tprivate cornerTargets(options: { refs?: readonly VectorAnchorRef[]; ids?: readonly string[] }): VectorAnchorRef[] {\n\t\tif (options.refs) return options.refs.filter((r) => getAnchor(this.doc, r));\n\t\tif (!options.ids && this.tool === 'direct' && this.anchors.length) return this.anchors.slice();\n\t\tconst out: VectorAnchorRef[] = [];\n\t\tfor (const id of options.ids ?? this.selection) {\n\t\t\tconst node = this.getNode(id);\n\t\t\tif (!node) continue;\n\t\t\tfor (const p of collectPathNodes(node)) {\n\t\t\t\tif (p.locked) continue;\n\t\t\t\tp.subpaths.forEach((sp, s) => sp.anchors.forEach((_, i) => out.push({ nodeId: p.id, subpath: s, index: i })));\n\t\t\t}\n\t\t}\n\t\treturn out;\n\t}\n\n\t/**\n\t * Outline Stroke: replaces each selected (or given) path's stroke with a filled path of the\n\t * area it paints (alignment, joins, caps, dashes and arrowheads included), placed above a copy\n\t * of the path that keeps the fill (left out when there is none). Returns the new paths' ids.\n\t */\n\toutlineStroke(options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst roots = options.ids ?? this.selectedRoots();\n\t\tconst paths: VectorPathNode[] = [];\n\t\tfor (const id of roots) {\n\t\t\tconst node = this.getNode(id);\n\t\t\tif (node) for (const p of collectPathNodes(node)) if (!p.locked && hasPaint(p.stroke) && (p.strokeWidth ?? 1) > 0) paths.push(p);\n\t\t}\n\t\tif (!paths.length) return [];\n\t\tconst created: string[] = [];\n\t\tconst replace = new Map<string, VectorNode[]>();\n\t\tfor (const p of paths) {\n\t\t\t// In the node's own space: the new paths keep its transform.\n\t\t\t// As drawn: path effects applied (the outline itself carries none).\n\t\t\tconst outline = bezierToSubpaths(vectorStrokeOutlinePath(vectorAppearanceOutline(p), p));\n\t\t\tif (!outline.length) continue;\n\t\t\tconst {\n\t\t\t\tstroke,\n\t\t\t\tstrokeWidth: _w,\n\t\t\t\tstrokeLinecap: _c,\n\t\t\t\tstrokeLinejoin: _j,\n\t\t\t\tstrokeDasharray: _d,\n\t\t\t\tstrokeAlign: _a,\n\t\t\t\tstartArrow: _s,\n\t\t\t\tendArrow: _e,\n\t\t\t\tarrowAlign: _al,\n\t\t\t\tstartArrowScale: _ss,\n\t\t\t\tendArrowScale: _es,\n\t\t\t\tstrokeWidthProfile: _wp,\n\t\t\t\teffects,\n\t\t\t\t...rest\n\t\t\t} = cloneVectorNode(p);\n\t\t\tconst id = this.newId();\n\t\t\tconst strokeNode: VectorPathNode = { ...rest, id, name: `${p.name ?? 'Path'} Stroke`, subpaths: outline, fill: stroke ?? null, stroke: null, fillRule: 'nonzero' };\n\t\t\tconst list: VectorNode[] = hasPaint(p.fill) ? [{ ...rest, ...(effects ? { effects } : {}), stroke: null }, strokeNode] : [strokeNode];\n\t\t\tcreated.push(id);\n\t\t\treplace.set(p.id, list);\n\t\t}\n\t\tif (!created.length) return [];\n\t\tthis.change('Outline Stroke', () => {\n\t\t\tlet nodes = this.doc.nodes;\n\t\t\tfor (const [id, list] of replace) nodes = spliceTree(nodes, id, list);\n\t\t\tthis.setDoc(nodes);\n\t\t\tthis.selection = created;\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn created;\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Path effects\n\t// -------------------------------------------------------------------------\n\n\t/** Selected (or given) nodes that can carry effects: the roots of the selection. */\n\tprivate effectTargets(ids?: readonly string[]): VectorNode[] {\n\t\treturn (ids ?? this.selectedRoots()).map((id) => this.getNode(id)).filter((n): n is VectorNode => !!n && !n.blendStep);\n\t}\n\n\t/**\n\t * Adds an effect of `type` to the selected (or given) nodes, at the bottom of each stack (or\n\t * at `index`), with default parameters merged with `params`. A repeater's copies start one\n\t * shape width apart. One undo step; returns the new effect ids (one per node, in order).\n\t */\n\taddEffect(type: VectorEffectType, options: { ids?: readonly string[]; params?: Record<string, unknown>; index?: number } = {}): string[] {\n\t\tconst targets = this.effectTargets(options.ids);\n\t\tif (!targets.length || !createVectorEffect(type, '')) return [];\n\t\tconst created: string[] = [];\n\t\tconst next = new Map<string, VectorNode>();\n\t\tfor (const node of targets) {\n\t\t\tconst list = node.effects ?? [];\n\t\t\tlet k = list.length + 1;\n\t\t\twhile (list.some((e) => e.id === `fx${k}`)) k++;\n\t\t\tconst params: Record<string, unknown> = { ...(options.params ?? {}) };\n\t\t\tif (type === 'repeater' && params.position === undefined) {\n\t\t\t\tconst b = bezierPathBounds(vectorAppearanceOutline(node));\n\t\t\t\tif (b && b.maxX - b.minX > 0) params.position = { x: Math.round((b.maxX - b.minX) * 1.25 * 100) / 100, y: 0 };\n\t\t\t}\n\t\t\tconst effect = createVectorEffect(type, `fx${k}`, params)!;\n\t\t\tconst at = options.index === undefined ? list.length : Math.min(list.length, Math.max(0, Math.round(options.index)));\n\t\t\tnext.set(node.id, { ...node, effects: [...list.slice(0, at), effect, ...list.slice(at)] } as VectorNode);\n\t\t\tcreated.push(effect.id);\n\t\t}\n\t\tthis.change(`Add ${VECTOR_EFFECT_LABELS[type]}`, () => this.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n)));\n\t\treturn created;\n\t}\n\n\tprivate editEffects(nodeId: string, label: string, fn: (effects: VectorEffect[]) => VectorEffect[] | string, merge?: string): VectorActionResult {\n\t\tconst node = this.getNode(nodeId);\n\t\tif (!node) return { ok: false, message: `There is no node \"${nodeId}\".` };\n\t\tconst result = fn(node.effects ?? []);\n\t\tif (typeof result === 'string') return { ok: false, message: result };\n\t\tthis.change(label, () => this.replaceNode(nodeId, (result.length ? { ...node, effects: result } : (({ effects: _e, ...rest }) => rest)(node)) as VectorNode), merge);\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * Turns an effect on or off and changes its parameters (unknown or invalid values are\n\t * ignored). With `merge` (true, or a key of your own), consecutive updates of the same effect\n\t * fold into one undo step, as a slider drag should; call `endMergedEdit` when it ends.\n\t */\n\tupdateEffect(nodeId: string, effectId: string, patch: { enabled?: boolean; params?: Record<string, unknown> }, options: { merge?: boolean | string } = {}): VectorActionResult {\n\t\tconst merge = options.merge === true ? `effect:${nodeId}:${effectId}` : options.merge || undefined;\n\t\tconst node = this.getNode(nodeId);\n\t\tconst current = node?.effects?.find((e) => e.id === effectId);\n\t\tconst label = current ? VECTOR_EFFECT_LABELS[current.type] ?? 'Effect' : 'Effect';\n\t\treturn this.editEffects(\n\t\t\tnodeId,\n\t\t\t`Edit ${label}`,\n\t\t\t(list) => {\n\t\t\t\tconst i = list.findIndex((e) => e.id === effectId);\n\t\t\t\tif (i < 0) return `Node \"${nodeId}\" has no effect \"${effectId}\".`;\n\t\t\t\tconst e = list[i];\n\t\t\t\tconst params = patch.params ? normalizeVectorEffectParams(e.type, patch.params, e.params as never) : e.params;\n\t\t\t\tconst next = { ...e, params, ...(patch.enabled !== undefined ? { enabled: patch.enabled } : {}) } as VectorEffect;\n\t\t\t\tif (next.enabled !== false) delete (next as { enabled?: boolean }).enabled;\n\t\t\t\treturn list.map((x, j) => (j === i ? next : x));\n\t\t\t},\n\t\t\tmerge\n\t\t);\n\t}\n\n\t/** Removes an effect from a node's stack. */\n\tremoveEffect(nodeId: string, effectId: string): VectorActionResult {\n\t\treturn this.editEffects(nodeId, 'Remove Effect', (list) => (list.some((e) => e.id === effectId) ? list.filter((e) => e.id !== effectId) : `Node \"${nodeId}\" has no effect \"${effectId}\".`));\n\t}\n\n\t/** Moves an effect to position `index` of its stack (0 is applied first). */\n\tmoveEffect(nodeId: string, effectId: string, index: number): VectorActionResult {\n\t\treturn this.editEffects(nodeId, 'Reorder Effects', (list) => {\n\t\t\tconst i = list.findIndex((e) => e.id === effectId);\n\t\t\tif (i < 0) return `Node \"${nodeId}\" has no effect \"${effectId}\".`;\n\t\t\tconst to = Math.min(list.length - 1, Math.max(0, Math.round(index)));\n\t\t\tconst out = list.slice();\n\t\t\tconst [e] = out.splice(i, 1);\n\t\t\tout.splice(to, 0, e);\n\t\t\treturn out;\n\t\t});\n\t}\n\n\t/**\n\t * Expand Appearance: bakes the path effects and live booleans of the selected (or given)\n\t * nodes, and of everything inside them, into plain paths. A node keeps its id (a group when\n\t * it draws as several shapes); the shapes it made get new ids. Returns the ids of the expanded\n\t * nodes (empty when none was live).\n\t */\n\texpandAppearance(options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst roots = (options.ids ?? this.selectedRoots()).map((id) => this.getNode(id)).filter((n): n is VectorNode => !!n && hasVectorAppearance(n));\n\t\tif (!roots.length) return [];\n\t\tconst fresh = (n: VectorNode): VectorNode => {\n\t\t\tconst id = n.id.includes('~fx') ? this.newId() : n.id;\n\t\t\treturn n.type === 'group' ? { ...n, id, children: n.children.map(fresh) } : { ...n, id };\n\t\t};\n\t\tconst bake = (n: VectorNode): VectorNode => {\n\t\t\tif (!hasVectorAppearance(n)) return n;\n\t\t\tif (!hasOwnVectorAppearance(n)) return { ...(n as VectorGroupNode), children: (n as VectorGroupNode).children.map(bake) };\n\t\t\tlet e = expandVectorAppearance(n);\n\t\t\t// A live group that draws as one shape becomes that path, in the group's place.\n\t\t\tif (e.type === 'group' && e.children.length === 1 && e.children[0].type === 'path') {\n\t\t\t\tconst { id: _id, opacity, name, ...child } = e.children[0];\n\t\t\t\tconst k = (e.opacity ?? 1) * (opacity ?? 1);\n\t\t\t\te = { ...child, id: e.id, name: e.name ?? name, ...placementOf(e), ...(k < 1 ? { opacity: k } : {}) } as VectorPathNode;\n\t\t\t}\n\t\t\treturn fresh(e);\n\t\t};\n\t\tconst next = new Map(roots.map((n) => [n.id, bake(n)]));\n\t\tthis.change('Expand Appearance', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n));\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn roots.map((n) => n.id);\n\t}\n\n\trename(id: string, name: string): VectorActionResult {\n\t\treturn this.updateNode(id, { name }, { label: 'Rename' });\n\t}\n\n\tsetVisible(id: string, visible: boolean): VectorActionResult {\n\t\treturn this.updateNode(id, { visible }, { label: visible ? 'Show' : 'Hide' });\n\t}\n\n\tsetLocked(id: string, locked: boolean): VectorActionResult {\n\t\tconst result = this.updateNode(id, { locked }, { label: locked ? 'Lock' : 'Unlock' });\n\t\tif (locked && result.ok && this.selection.includes(id)) {\n\t\t\tthis.selection = this.selection.filter((s) => s !== id);\n\t\t\tthis.notify();\n\t\t}\n\t\treturn result;\n\t}\n\n\t/** Removes nodes by id. */\n\tremoveNodes(ids: readonly string[], options: { label?: string } = {}): VectorActionResult {\n\t\tconst drop = new Set(ids.filter((id) => this.getNode(id)));\n\t\tif (!drop.size) return { ok: false, message: 'Nothing to delete.' };\n\t\tthis.change(options.label ?? 'Delete', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (drop.has(n.id) ? null : n)));\n\t\t\tthis.selection = this.selection.filter((id) => !drop.has(id) && this.getNode(id));\n\t\t\tthis.anchors = this.anchors.filter((r) => getAnchor(this.doc, r));\n\t\t\tif (this.editingId && !this.getNode(this.editingId)) this.editingId = null;\n\t\t\tif (this.pen && !this.getNode(this.pen.nodeId)) this.pen = null;\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Selection\n\t// -------------------------------------------------------------------------\n\n\tselect(ids: readonly string[], options: { add?: boolean; toggle?: boolean } = {}): void {\n\t\tconst valid = ids.filter((id) => this.getNode(id));\n\t\tlet next: string[];\n\t\tif (options.toggle) {\n\t\t\tconst set = new Set(this.selection);\n\t\t\tfor (const id of valid) set.has(id) ? set.delete(id) : set.add(id);\n\t\t\tnext = [...set];\n\t\t} else next = options.add ? [...new Set([...this.selection, ...valid])] : valid;\n\t\tthis.selection = next;\n\t\tif (!options.add && !options.toggle) this.anchors = this.anchors.filter((r) => next.includes(r.nodeId));\n\t\tthis.notify();\n\t}\n\n\tselectAll(): void {\n\t\tif (this.tool === 'direct' && this.editingId) {\n\t\t\tthis.anchors = getVisibleAnchors(this.view());\n\t\t} else {\n\t\t\tthis.selection = this.doc.nodes.filter((n) => !n.locked && n.visible !== false).map((n) => n.id);\n\t\t}\n\t\tthis.notify();\n\t}\n\n\tclearSelection(): void {\n\t\tif (!this.selection.length && !this.anchors.length && !this.editingId) return;\n\t\tthis.selection = [];\n\t\tthis.anchors = [];\n\t\tthis.editingId = null;\n\t\tthis.notify();\n\t}\n\n\tselectAnchors(refs: readonly VectorAnchorRef[], options: { add?: boolean } = {}): void {\n\t\tconst valid = refs.filter((r) => getAnchor(this.doc, r));\n\t\tthis.anchors = options.add ? [...this.anchors, ...valid.filter((r) => !this.anchors.some((a) => sameRef(a, r)))] : valid;\n\t\tif (valid.length && !this.editingId) this.editingId = valid[0].nodeId;\n\t\tthis.notify();\n\t}\n\n\t/** Top-level ids of the selection (members of selected groups are covered by the group). */\n\tprivate selectedRoots(): string[] {\n\t\treturn this.selection.filter((id) => {\n\t\t\tlet covered = false;\n\t\t\tconst loc = locateVectorNode(this.doc.nodes, id);\n\t\t\tlet parent = loc?.parent ?? null;\n\t\t\twhile (parent) {\n\t\t\t\tif (this.selection.includes(parent.id)) covered = true;\n\t\t\t\tparent = locateVectorNode(this.doc.nodes, parent.id)?.parent ?? null;\n\t\t\t}\n\t\t\treturn !covered;\n\t\t});\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Transforms\n\t// -------------------------------------------------------------------------\n\n\t/** `m`, given in a frame rotated by `angle`, as a document-space matrix. */\n\tprivate inFrame(angle: number, m: AffineMatrix): AffineMatrix {\n\t\tif (!angle) return m;\n\t\treturn multiplyMatrices(multiplyMatrices(rotateMatrix(-angle), m), rotateMatrix(angle));\n\t}\n\n\t/** Applies a matrix to the selected (or given) nodes as one undo step. */\n\ttransform(matrix: AffineMatrix, options: { ids?: readonly string[]; label?: string } = {}): VectorActionResult {\n\t\tconst ids = new Set(options.ids ?? this.selectedRoots());\n\t\tif (!ids.size) return { ok: false, message: 'Nothing is selected.' };\n\t\tthis.change(options.label ?? 'Transform', () => {\n\t\t\tthis.doc = this.transformed(this.doc, ids, matrix);\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** `doc` with nodes `ids` moved by `m`, given in document space. */\n\tprivate transformed(doc: VectorDocument, ids: ReadonlySet<string>, m: AffineMatrix): VectorDocument {\n\t\tconst visit = (list: readonly VectorNode[], parent: AffineMatrix): VectorNode[] => {\n\t\t\tlet changed = false;\n\t\t\tconst out = list.map((n) => {\n\t\t\t\tlet next = n;\n\t\t\t\tif (ids.has(n.id) && !n.locked) {\n\t\t\t\t\t// `m` in the node's parent space: parent → document, m, document → parent.\n\t\t\t\t\tconst local = isRoot(parent) ? m : multiplyMatrices(multiplyMatrices(parent, m), invertMatrix(parent));\n\t\t\t\t\tnext = transformVectorNode(n, local);\n\t\t\t\t} else if (n.type === 'group') {\n\t\t\t\t\tconst children = visit(n.children, multiplyMatrices(nodeMatrix(n), parent));\n\t\t\t\t\tif (children !== n.children) next = { ...n, children };\n\t\t\t\t}\n\t\t\t\tif (next !== n) changed = true;\n\t\t\t\treturn next;\n\t\t\t});\n\t\t\treturn changed ? out : (list as VectorNode[]);\n\t\t};\n\t\tconst nodes = visit(doc.nodes, IDENTITY_MATRIX);\n\t\treturn nodes === doc.nodes ? doc : { ...doc, nodes };\n\t}\n\n\ttranslate(dx: number, dy: number, options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tif (this.tool === 'direct' && this.anchors.length && !options.ids) {\n\t\t\tthis.change('Move Points', () => {\n\t\t\t\tthis.doc = this.movedAnchors(this.doc, this.anchors, dx, dy);\n\t\t\t});\n\t\t\treturn { ok: true };\n\t\t}\n\t\treturn this.transform(translateMatrix(dx, dy), { ids: options.ids, label: 'Move' });\n\t}\n\n\t/** The selection frame's centre in document space. */\n\tprivate frameCenter(frame: VectorSelectionFrame): Vec2 {\n\t\treturn applyMatrix(rotateMatrix(frame.angle), { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 });\n\t}\n\n\t/**\n\t * Scales along the selection box's own axes (rotated with the selection when its nodes\n\t * share a rotation) around `origin` (default: the box centre, in document space).\n\t */\n\tscale(sx: number, sy: number = sx, options: { ids?: readonly string[]; origin?: Vec2 } = {}): VectorActionResult {\n\t\tconst frame = this.frameOf(options.ids ?? this.selectedRoots());\n\t\tif (!frame) return { ok: false, message: 'Nothing is selected.' };\n\t\tconst origin = applyMatrix(rotateMatrix(-frame.angle), options.origin ?? this.frameCenter(frame));\n\t\treturn this.transform(this.inFrame(frame.angle, scaleMatrix(sx, sy, origin)), { ids: options.ids, label: 'Scale' });\n\t}\n\n\t/** Rotates clockwise by `degrees` around `origin` (default: the selection centre). */\n\trotate(degrees: number, options: { ids?: readonly string[]; origin?: Vec2 } = {}): VectorActionResult {\n\t\tconst frame = this.frameOf(options.ids ?? this.selectedRoots());\n\t\tif (!frame) return { ok: false, message: 'Nothing is selected.' };\n\t\tconst origin = options.origin ?? this.frameCenter(frame);\n\t\treturn this.transform(rotateMatrix(degrees, origin), { ids: options.ids, label: 'Rotate' });\n\t}\n\n\t/** Sets each selected (or given) node's rotation to `degrees`, turning it around its own centre. */\n\tsetRotation(degrees: number, options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst ids = options.ids ?? this.selectedRoots();\n\t\tif (!ids.length) return { ok: false, message: 'Nothing is selected.' };\n\t\tthis.change('Set Rotation', () => {\n\t\t\tfor (const id of ids) {\n\t\t\t\tconst frame = this.frameOf([id]);\n\t\t\t\tif (!frame) continue;\n\t\t\t\tconst delta = normalizeAngle(degrees - frame.angle);\n\t\t\t\tif (Math.abs(delta) > 1e-9) this.doc = this.transformed(this.doc, new Set([id]), rotateMatrix(delta, this.frameCenter(frame)));\n\t\t\t}\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Mirrors along the selection box's own axes. */\n\tflip(axis: 'horizontal' | 'vertical', options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst frame = this.frameOf(options.ids ?? this.selectedRoots());\n\t\tif (!frame) return { ok: false, message: 'Nothing is selected.' };\n\t\tconst c = { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 };\n\t\tconst m = axis === 'horizontal' ? scaleMatrix(-1, 1, c) : scaleMatrix(1, -1, c);\n\t\treturn this.transform(this.inFrame(frame.angle, m), { ids: options.ids, label: axis === 'horizontal' ? 'Flip Horizontal' : 'Flip Vertical' });\n\t}\n\n\t/**\n\t * Aligns the selected nodes to their combined bounds, or to the artboard when one node is\n\t * selected (or `to: 'artboard'`).\n\t */\n\talign(edge: VectorAlign, options: { ids?: readonly string[]; to?: 'selection' | 'artboard' } = {}): VectorActionResult {\n\t\tconst ids = options.ids ?? this.selectedRoots();\n\t\tif (!ids.length) return { ok: false, message: 'Nothing is selected.' };\n\t\tconst target = options.to === 'artboard' || ids.length === 1 ? { x: 0, y: 0, width: this.doc.width, height: this.doc.height } : this.boundsOf(ids)!;\n\t\tthis.change(`Align ${edge[0].toUpperCase()}${edge.slice(1)}`, () => {\n\t\t\tfor (const id of ids) {\n\t\t\t\tconst b = this.boundsOf([id]);\n\t\t\t\tif (!b) continue;\n\t\t\t\tlet dx = 0;\n\t\t\t\tlet dy = 0;\n\t\t\t\tif (edge === 'left') dx = target.x - b.x;\n\t\t\t\tif (edge === 'center') dx = target.x + target.width / 2 - (b.x + b.width / 2);\n\t\t\t\tif (edge === 'right') dx = target.x + target.width - (b.x + b.width);\n\t\t\t\tif (edge === 'top') dy = target.y - b.y;\n\t\t\t\tif (edge === 'middle') dy = target.y + target.height / 2 - (b.y + b.height / 2);\n\t\t\t\tif (edge === 'bottom') dy = target.y + target.height - (b.y + b.height);\n\t\t\t\tif (dx || dy) this.doc = this.transformed(this.doc, new Set([id]), translateMatrix(dx, dy));\n\t\t\t}\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Spaces three or more selected nodes evenly (equal gaps). */\n\tdistribute(direction: VectorDistribute, options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst ids = options.ids ?? this.selectedRoots();\n\t\tconst items = ids.map((id) => ({ id, b: this.boundsOf([id]) })).filter((i): i is { id: string; b: VectorBounds } => !!i.b);\n\t\tif (items.length < 3) return { ok: false, message: 'Select at least three objects to distribute.' };\n\t\tconst horizontal = direction === 'horizontal';\n\t\tconst pos = (b: VectorBounds) => (horizontal ? b.x : b.y);\n\t\tconst size = (b: VectorBounds) => (horizontal ? b.width : b.height);\n\t\titems.sort((a, b) => pos(a.b) - pos(b.b));\n\t\tconst first = items[0].b;\n\t\tconst last = items[items.length - 1].b;\n\t\tconst total = pos(last) + size(last) - pos(first);\n\t\tconst gap = (total - items.reduce((s, i) => s + size(i.b), 0)) / (items.length - 1);\n\t\tthis.change(horizontal ? 'Distribute Horizontally' : 'Distribute Vertically', () => {\n\t\t\tlet cursor = pos(first);\n\t\t\tfor (const item of items) {\n\t\t\t\tconst d = cursor - pos(item.b);\n\t\t\t\tif (Math.abs(d) > 1e-9) this.doc = this.transformed(this.doc, new Set([item.id]), horizontal ? translateMatrix(d, 0) : translateMatrix(0, d));\n\t\t\t\tcursor += size(item.b) + gap;\n\t\t\t}\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Structure\n\t// -------------------------------------------------------------------------\n\n\t/** Copies the selection (offset by `offset`, default 10, 10) on top of each original. */\n\tduplicate(options: { ids?: readonly string[]; offset?: Vec2 } = {}): string[] {\n\t\tconst ids = options.ids ?? this.selectedRoots();\n\t\tconst offset = options.offset ?? { x: 10, y: 10 };\n\t\tconst created: string[] = [];\n\t\tif (!ids.length) return created;\n\t\tthis.change('Duplicate', () => {\n\t\t\tthis.doc = this.withDuplicates(this.doc, ids, offset, created);\n\t\t\tthis.selection = created;\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn created;\n\t}\n\n\tprivate withDuplicates(doc: VectorDocument, ids: readonly string[], offset: Vec2, created: string[]): VectorDocument {\n\t\tconst set = new Set(ids);\n\t\tconst m = translateMatrix(offset.x, offset.y);\n\t\tconst insert = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\tconst out: VectorNode[] = [];\n\t\t\tfor (const node of list) {\n\t\t\t\tconst n = node.type === 'group' ? { ...node, children: insert(node.children) } : node;\n\t\t\t\tout.push(n);\n\t\t\t\tif (set.has(node.id)) {\n\t\t\t\t\tconst copy = transformVectorNode(this.freshIds(node), m);\n\t\t\t\t\tcreated.push(copy.id);\n\t\t\t\t\tout.push(copy);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out;\n\t\t};\n\t\treturn { ...doc, nodes: insert(doc.nodes) };\n\t}\n\n\t/** Groups the selected nodes (they must share a parent). Returns the group id. */\n\tgroup(options: { ids?: readonly string[]; name?: string } = {}): string | null {\n\t\tconst ids = options.ids ?? this.selectedRoots();\n\t\tif (!ids.length) return null;\n\t\tconst locs = ids.map((id) => locateVectorNode(this.doc.nodes, id));\n\t\tif (locs.some((l) => !l)) return null;\n\t\tconst parent = locs[0]!.parent?.id ?? null;\n\t\tif (locs.some((l) => (l!.parent?.id ?? null) !== parent)) return null;\n\t\tconst id = this.newId();\n\t\tthis.change('Group', () => {\n\t\t\tconst set = new Set(ids);\n\t\t\tconst regroup = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\t\tconst members = list.filter((n) => set.has(n.id));\n\t\t\t\tconst top = Math.max(...members.map((m) => list.indexOf(m)));\n\t\t\t\tconst out: VectorNode[] = [];\n\t\t\t\tlist.forEach((n, i) => {\n\t\t\t\t\tif (i === top) out.push({ id, type: 'group', name: options.name ?? 'Group', children: members });\n\t\t\t\t\tif (!set.has(n.id)) out.push(n);\n\t\t\t\t});\n\t\t\t\treturn out;\n\t\t\t};\n\t\t\tif (parent === null) this.setDoc(regroup(this.doc.nodes));\n\t\t\telse this.setDoc(mapTree(this.doc.nodes, (n) => (n.id === parent && n.type === 'group' ? { ...n, children: regroup(n.children) } : n)));\n\t\t\tthis.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** Replaces selected groups with their children. */\n\tungroup(options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst groups = (options.ids ?? this.selection).map((id) => this.getNode(id)).filter((n): n is VectorGroupNode => n?.type === 'group');\n\t\tif (!groups.length) return { ok: false, message: 'Select a group to ungroup.' };\n\t\tconst set = new Set(groups.map((g) => g.id));\n\t\tthis.change('Ungroup', () => {\n\t\t\tconst flatten = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\t\tconst out: VectorNode[] = [];\n\t\t\t\tlet changed = false;\n\t\t\t\tfor (const n of list) {\n\t\t\t\t\tif (set.has(n.id) && n.type === 'group') {\n\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t\tconst opacity = n.opacity;\n\t\t\t\t\t\tconst m = nodeMatrix(n);\n\t\t\t\t\t\tout.push(\n\t\t\t\t\t\t\t...n.children\n\t\t\t\t\t\t\t\t.map((c) => (c.blendStep ? (({ blendStep: _s, locked: _l, ...rest }) => rest as VectorNode)(c) : c))\n\t\t\t\t\t\t\t\t.map((c) => (c.booleanOperation ? (({ booleanOperation: _b, ...rest }) => rest as VectorNode)(c) : c))\n\t\t\t\t\t\t\t\t.map((c) => (n.transform ? transformVectorNode(c, m) : c))\n\t\t\t\t\t\t\t\t.map((c) => (opacity !== undefined && opacity < 1 ? { ...c, opacity: (c.opacity ?? 1) * opacity } : c))\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (n.type === 'group') {\n\t\t\t\t\t\tconst children = flatten(n.children);\n\t\t\t\t\t\tif (children !== n.children) changed = true;\n\t\t\t\t\t\tout.push(children !== n.children ? { ...n, children } : n);\n\t\t\t\t\t} else out.push(n);\n\t\t\t\t}\n\t\t\t\treturn changed ? out : (list as VectorNode[]);\n\t\t\t};\n\t\t\tthis.setDoc(flatten(this.doc.nodes));\n\t\t\tthis.selection = groups.flatMap((g) => g.children.map((c) => c.id));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Changes stacking order within each node's parent. */\n\tarrange(op: VectorArrange, options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst set = new Set(options.ids ?? this.selectedRoots());\n\t\tif (!set.size) return { ok: false, message: 'Nothing is selected.' };\n\t\tconst reorder = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\tconst out = list.map((n) => (n.type === 'group' ? { ...n, children: reorder(n.children) } : n));\n\t\t\tif (op === 'front') return [...out.filter((n) => !set.has(n.id)), ...out.filter((n) => set.has(n.id))];\n\t\t\tif (op === 'back') return [...out.filter((n) => set.has(n.id)), ...out.filter((n) => !set.has(n.id))];\n\t\t\tif (op === 'forward') {\n\t\t\t\tfor (let i = out.length - 2; i >= 0; i--) if (set.has(out[i].id) && !set.has(out[i + 1].id)) [out[i], out[i + 1]] = [out[i + 1], out[i]];\n\t\t\t} else {\n\t\t\t\tfor (let i = 1; i < out.length; i++) if (set.has(out[i].id) && !set.has(out[i - 1].id)) [out[i], out[i - 1]] = [out[i - 1], out[i]];\n\t\t\t}\n\t\t\treturn out;\n\t\t};\n\t\tconst labels = { front: 'Bring to Front', forward: 'Bring Forward', backward: 'Send Backward', back: 'Send to Back' };\n\t\tthis.change(labels[op], () => this.setDoc(reorder(this.doc.nodes)));\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * Combines the selected paths (two or more) into one with a boolean operation. `subtract`\n\t * removes the upper shapes from the lowest one. The result takes the lowest shape's style\n\t * and place. Shapes take part as drawn (path effects and live booleans applied).\n\t *\n\t * With `live`, the operands stay editable instead: a lone live boolean group gets the\n\t * operation (its operands follow it again), operands of live boolean groups get it as their\n\t * own, and anything else (two or more nodes with one parent) is wrapped in a new live boolean\n\t * group. Returns the result's (or the live group's) id, or null.\n\t */\n\tbooleanOp(op: VectorBooleanOperation, options: { ids?: readonly string[]; live?: boolean } = {}): string | null {\n\t\tconst roots = options.ids ?? this.selectedRoots();\n\t\tif (options.live) return this.liveBooleanOp(op, roots);\n\t\tconst entries = vectorPathEntries(this.doc, roots, { locked: true });\n\t\tif (entries.length < 2) return null;\n\t\tconst result = pathBoolean(\n\t\t\tBOOLEAN_OPS[op],\n\t\t\tentries.map((e) => ({\n\t\t\t\tpath: vectorPathWorldBezier({ ...e.node, subpaths: e.node.subpaths.filter((s) => s.anchors.length > 1).map((s) => ({ ...s, closed: true })) }, e.parent),\n\t\t\t\tfillRule: e.node.fillRule ?? 'nonzero'\n\t\t\t}))\n\t\t);\n\t\tconst base = entries[0].node;\n\t\tconst anchorRoot = roots.find((r) => this.holds(r, base.id)) ?? base.id;\n\t\tconst subpaths = bezierToSubpaths(this.intoParentSpace(anchorRoot, result.path));\n\t\tif (result.error || !subpaths.length) return null;\n\t\tconst id = this.newId();\n\t\tconst { transform: _t, effects: _e, booleanOperation: _b, ...baseRest } = cloneVectorNode(base);\n\t\tconst node: VectorPathNode = { ...baseRest, id, name: BOOLEAN_LABELS[op], subpaths };\n\t\tconst drop = new Set([...entries.map((e) => e.node.id), ...roots]);\n\t\tthis.change(BOOLEAN_LABELS[op], () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === anchorRoot ? node : drop.has(n.id) ? null : n)));\n\t\t\tthis.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** Whether stored node `rootId` holds `id` as drawn (`id` may be an expanded path inside it). */\n\tprivate holds(rootId: string, id: string): boolean {\n\t\tconst n = this.getNode(rootId);\n\t\treturn !!n && (!!findVectorNode([n], id) || !!findVectorNode([expandVectorAppearance(n)], id));\n\t}\n\n\tprivate liveBooleanOp(op: VectorBooleanOperation, roots: readonly string[]): string | null {\n\t\tconst nodes = roots.map((id) => this.getNode(id));\n\t\tif (!nodes.length || nodes.some((n) => !n)) return null;\n\t\tconst label = `Live ${BOOLEAN_LABELS[op]}`;\n\t\tconst lone = nodes.length === 1 ? nodes[0] : null;\n\t\tif (isVectorLiveBoolean(lone)) {\n\t\t\tconst children = lone.children.map((c) => (c.booleanOperation ? (({ booleanOperation: _b, ...rest }) => rest as VectorNode)(c) : c));\n\t\t\tthis.change(label, () => this.replaceNode(lone.id, { ...lone, liveBoolean: op, children }));\n\t\t\treturn lone.id;\n\t\t}\n\t\tconst parents = roots.map((id) => locateVectorNode(this.doc.nodes, id)?.parent ?? null);\n\t\tif (parents.every((g) => isVectorLiveBoolean(g))) {\n\t\t\tconst set = new Set(roots);\n\t\t\tthis.change(label, () =>\n\t\t\t\tthis.setDoc(\n\t\t\t\t\tmapTree(this.doc.nodes, (n) => {\n\t\t\t\t\t\tif (n.type !== 'group' || !n.liveBoolean || !n.children.some((c) => set.has(c.id))) return n;\n\t\t\t\t\t\tconst group = n;\n\t\t\t\t\t\tconst children = group.children.map((c) => {\n\t\t\t\t\t\t\tif (!set.has(c.id)) return c;\n\t\t\t\t\t\t\tconst { booleanOperation: _b, ...rest } = c;\n\t\t\t\t\t\t\treturn (op === group.liveBoolean ? rest : { ...rest, booleanOperation: op }) as VectorNode;\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn { ...group, children };\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t);\n\t\t\treturn parents[0]!.id;\n\t\t}\n\t\tif (nodes.length < 2) return null;\n\t\tconst parent = parents[0]?.id ?? null;\n\t\tif (parents.some((g) => (g?.id ?? null) !== parent)) return null;\n\t\tconst id = this.newId();\n\t\tthis.change(label, () => {\n\t\t\tconst set = new Set(roots);\n\t\t\tconst wrap = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\t\tconst members = list.filter((n) => set.has(n.id)).map((c) => (c.booleanOperation ? (({ booleanOperation: _b, ...rest }) => rest as VectorNode)(c) : c));\n\t\t\t\tconst top = Math.max(...list.map((n, i) => (set.has(n.id) ? i : -1)));\n\t\t\t\tconst out: VectorNode[] = [];\n\t\t\t\tlist.forEach((n, i) => {\n\t\t\t\t\tif (i === top) out.push({ id, type: 'group', name: BOOLEAN_LABELS[op], liveBoolean: op, children: members });\n\t\t\t\t\tif (!set.has(n.id)) out.push(n);\n\t\t\t\t});\n\t\t\t\treturn out;\n\t\t\t};\n\t\t\tif (parent === null) this.setDoc(wrap(this.doc.nodes));\n\t\t\telse this.setDoc(mapTree(this.doc.nodes, (n) => (n.id === parent && n.type === 'group' ? { ...n, children: wrap(n.children) } : n)));\n\t\t\tthis.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** Joins the selected paths into one compound path (subpaths kept, lowest style). */\n\tcombine(options: { ids?: readonly string[] } = {}): string | null {\n\t\tconst roots = options.ids ?? this.selectedRoots();\n\t\tconst entries = vectorPathEntries(this.doc, roots, { locked: true });\n\t\tif (entries.length < 2) return null;\n\t\tconst paths = entries.map((e) => e.node);\n\t\tconst id = this.newId();\n\t\tconst first = roots.find((r) => this.holds(r, paths[0].id)) ?? paths[0].id;\n\t\tconst subpaths = entries.flatMap((e) => bezierToSubpaths(this.intoParentSpace(first, vectorPathWorldBezier(e.node, e.parent))));\n\t\tconst { transform: _t, effects: _e, booleanOperation: _b, ...baseRest } = cloneVectorNode(paths[0]);\n\t\tconst node: VectorPathNode = { ...baseRest, id, name: 'Compound Path', subpaths };\n\t\tconst drop = new Set([...paths.map((p) => p.id), ...roots]);\n\t\tthis.change('Combine', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === first ? node : drop.has(n.id) ? null : n)));\n\t\t\tthis.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** A document-space path mapped into the parent space of node `id`. */\n\tprivate intoParentSpace(id: string, path: BezierPath): BezierPath {\n\t\tconst parent = vectorParentMatrix(this.doc.nodes, id) ?? IDENTITY_MATRIX;\n\t\treturn isRoot(parent) ? path : transformBezierPath(path, invertMatrix(parent));\n\t}\n\n\t/** Splits selected compound paths into one path per subpath. */\n\tbreakApart(options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst paths = (options.ids ?? this.selection).map((id) => this.getNode(id)).filter((n): n is VectorPathNode => n?.type === 'path' && n.subpaths.length > 1);\n\t\tif (!paths.length) return { ok: false, message: 'Select a compound path to break apart.' };\n\t\tconst created: string[] = [];\n\t\tconst set = new Set(paths.map((p) => p.id));\n\t\tthis.change('Break Apart', () => {\n\t\t\tconst split = (list: readonly VectorNode[]): VectorNode[] =>\n\t\t\t\tlist.flatMap((n): VectorNode[] => {\n\t\t\t\t\tif (n.type === 'group') return [{ ...n, children: split(n.children) }];\n\t\t\t\t\tif (!set.has(n.id)) return [n];\n\t\t\t\t\treturn n.subpaths.map((sp, i) => {\n\t\t\t\t\t\tconst id = i === 0 ? n.id : this.newId();\n\t\t\t\t\t\tcreated.push(id);\n\t\t\t\t\t\treturn { ...n, id, subpaths: [sp] };\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\tthis.setDoc(split(this.doc.nodes));\n\t\t\tthis.selection = created;\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Pathfinder, Shape Builder and paint bucket\n\t// -------------------------------------------------------------------------\n\n\t/**\n\t * Pathfinder: Divide, Trim, Merge, Crop, Outline or Minus Back over the selected (or given)\n\t * paths, in document space (rotated nodes and groups included, live corners cut). Unfilled\n\t * stroked paths take part as the area their stroke paints (Divide: open ones cut instead).\n\t * The results replace the operands at the place of the top-most one, grouped when there are\n\t * several; each keeps the style of the operand whose area it is. Returns the new path ids,\n\t * or an empty list when nothing came out.\n\t */\n\tpathfinder(op: VectorPathfinderOperation, options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst roots = options.ids ?? this.selectedRoots();\n\t\tconst entries = vectorPathEntries(this.doc, roots);\n\t\tif (entries.length < (op === 'divide' || op === 'outline' ? 1 : 2)) return [];\n\t\tconst shapes = entries.map((e) => vectorPathfinderShape(e, op));\n\t\tconst result = runPathfinder(op, shapes);\n\t\tif (result.error || !result.pieces.length) return [];\n\t\tconst last = entries[entries.length - 1].node.id;\n\t\tconst topRoot = [...roots].reverse().find((r) => this.holds(r, last)) ?? last;\n\t\tconst nodes: VectorPathNode[] = [];\n\t\tfor (const piece of result.pieces) {\n\t\t\tconst subpaths = bezierToSubpaths(this.intoParentSpace(topRoot, piece.path)).map((sp) => (piece.open ? { ...sp, closed: false } : sp));\n\t\t\tif (!subpaths.length) continue;\n\t\t\tconst source = entries[piece.source].node;\n\t\t\tconst style = vectorResultStyle(source, shapes[piece.source].paint, op === 'outline');\n\t\t\tnodes.push({ ...style, type: 'path', id: this.newId(), name: source.name ?? 'Path', subpaths });\n\t\t}\n\t\tif (!nodes.length) return [];\n\t\tconst label = VECTOR_PATHFINDER_LABELS[op];\n\t\tconst container: VectorNode = nodes.length > 1 ? { id: this.newId(), type: 'group', name: label, children: nodes } : nodes[0];\n\t\tconst drop = new Set([...entries.map((e) => e.node.id), ...roots]);\n\t\tthis.change(label, () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === topRoot ? container : drop.has(n.id) ? null : n)));\n\t\t\tthis.selection = [container.id];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn nodes.map((n) => n.id);\n\t}\n\n\t/** The Shape Builder map of the selected (or given) paths, cached per document and selection. */\n\t// -------------------------------------------------------------------------\n\t// Blends\n\t// -------------------------------------------------------------------------\n\n\t/** Selected (or given) blend groups; a selected key or step counts as its blend. */\n\tprivate blendTargets(ids?: readonly string[]): (VectorGroupNode & { blend: VectorBlend })[] {\n\t\tconst out = new Map<string, VectorGroupNode & { blend: VectorBlend }>();\n\t\tfor (const id of ids ?? this.selection) {\n\t\t\tconst node = this.getNode(id);\n\t\t\tconst group = isVectorBlendGroup(node) ? node : locateVectorNode(this.doc.nodes, id)?.parent;\n\t\t\tif (isVectorBlendGroup(group)) out.set(group.id, group);\n\t\t}\n\t\treturn [...out.values()];\n\t}\n\n\t/**\n\t * Blends two or more selected (or given) paths or groups: they become the keys of a live\n\t * blend group (bottom to top, or in the order given with `keepOrder`) whose in-betweens are\n\t * regenerated whenever a key or the options change. The keys must share a parent. Returns\n\t * the blend group's id, or null.\n\t */\n\tblend(input: VectorBlendInput = {}): string | null {\n\t\tconst ids = [...new Set(input.ids ?? this.selectedRoots())].filter((id) => {\n\t\t\tconst n = this.getNode(id);\n\t\t\treturn !!n && !n.blendStep && !n.locked;\n\t\t});\n\t\tif (ids.length < 2) return null;\n\t\tconst locs = ids.map((id) => locateVectorNode(this.doc.nodes, id)!);\n\t\tconst parent = locs[0].parent?.id ?? null;\n\t\tif (locs.some((l) => (l.parent?.id ?? null) !== parent)) return null;\n\t\tconst order = input.keepOrder ? ids : ids.slice().sort((a, b) => locs[ids.indexOf(a)].index - locs[ids.indexOf(b)].index);\n\t\tconst blend: VectorBlend = blendOptionsOf({ ...this.blendDefaults, ...input });\n\t\tif (input.starts) {\n\t\t\tconst starts = Object.fromEntries(Object.entries(input.starts).filter(([k, v]) => order.includes(k) && Number.isInteger(v) && v >= 0));\n\t\t\tif (Object.keys(starts).length) blend.starts = starts;\n\t\t}\n\t\tconst id = this.newId();\n\t\tthis.change('Blend', () => {\n\t\t\tconst set = new Set(order);\n\t\t\tconst regroup = (list: readonly VectorNode[]): VectorNode[] => {\n\t\t\t\tconst members = order.map((m) => list.find((n) => n.id === m)!);\n\t\t\t\tconst top = Math.max(...members.map((m) => list.indexOf(m)));\n\t\t\t\tconst out: VectorNode[] = [];\n\t\t\t\tlist.forEach((n, i) => {\n\t\t\t\t\tif (i === top) out.push({ id, type: 'group', name: input.name ?? 'Blend', blend, children: members });\n\t\t\t\t\tif (!set.has(n.id)) out.push(n);\n\t\t\t\t});\n\t\t\t\treturn out;\n\t\t\t};\n\t\t\tif (parent === null) this.setDoc(regroup(this.doc.nodes));\n\t\t\telse this.setDoc(mapTree(this.doc.nodes, (n) => (n.id === parent && n.type === 'group' ? { ...n, children: regroup(n.children) } : n)));\n\t\t\tthis.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** Sets the options new blends get (spacing, steps, distance, orientation, morph). */\n\tsetBlendDefaults(patch: Partial<VectorBlendOptions>): void {\n\t\tthis.blendDefaults = { ...this.blendDefaults, ...blendOptionsOf(patch) };\n\t\tthis.notify();\n\t}\n\n\t/**\n\t * Adds a node as the last key of a blend (it must share the blend's parent); `start` is the\n\t * anchor of its first subpath the blend starts from. One undo step.\n\t */\n\taddBlendKey(blendId: string, nodeId: string, options: { start?: number | null } = {}): VectorActionResult {\n\t\tconst group = this.getNode(blendId);\n\t\tconst node = this.getNode(nodeId);\n\t\tif (!isVectorBlendGroup(group)) return { ok: false, message: `\"${blendId}\" is not a blend.` };\n\t\tif (!node || node.blendStep || node.locked || node.id === blendId) return { ok: false, message: `\"${nodeId}\" cannot be a key.` };\n\t\tconst parentOf = (id: string) => locateVectorNode(this.doc.nodes, id)?.parent?.id ?? null;\n\t\tif (parentOf(blendId) !== parentOf(nodeId)) return { ok: false, message: 'The new key must sit beside the blend (same parent).' };\n\t\tconst start = options.start;\n\t\tthis.change('Blend', () => {\n\t\t\tconst blend = start !== null && start !== undefined ? { ...group.blend, starts: { ...group.blend.starts, [nodeId]: start } } : group.blend;\n\t\t\t// The key leaves its place and joins the blend.\n\t\t\tconst m = invertMatrix(nodeMatrix(group));\n\t\t\tconst key = group.transform ? transformVectorNode(node, m) : node;\n\t\t\t// Two passes: the key must not be removed again from inside the blend.\n\t\t\tconst without = mapTree(this.doc.nodes, (n) => (n.id === nodeId ? null : n));\n\t\t\tthis.setDoc(spliceTree(without, blendId, [{ ...group, blend, children: [...group.children, key] }]));\n\t\t\tthis.selection = [blendId];\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Changes the spacing, steps, distance, orientation or morph of the selected (or given) blends. */\n\tsetBlendOptions(patch: Partial<VectorBlendOptions>, options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst groups = this.blendTargets(options.ids);\n\t\tif (!groups.length) return { ok: false, message: 'Select a blend.' };\n\t\tconst clean = blendOptionsOf(patch);\n\t\tif (!Object.keys(clean).length) return { ok: false, message: 'No valid blend options were given.' };\n\t\tconst set = new Set(groups.map((g) => g.id));\n\t\tthis.change('Blend Options', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (set.has(n.id) && n.type === 'group' && n.blend ? { ...n, blend: { ...n.blend, ...clean } } : n)));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Expand: turns the selected (or given) blends into plain groups of their keys and steps. Returns their ids. */\n\texpandBlend(options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst groups = this.blendTargets(options.ids);\n\t\tif (!groups.length) return [];\n\t\tconst set = new Set(groups.map((g) => g.id));\n\t\tconst used = new Set<string>();\n\t\twalkVectorNodes(this.doc.nodes, (n) => used.add(n.id));\n\t\tconst fresh = () => {\n\t\t\tlet id = this.createId();\n\t\t\twhile (used.has(id)) id = this.createId();\n\t\t\tused.add(id);\n\t\t\treturn id;\n\t\t};\n\t\tthis.change('Expand Blend', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (set.has(n.id) && n.type === 'group' ? expandVectorBlend(n, fresh) : n)));\n\t\t\tthis.selection = [...set];\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn [...set];\n\t}\n\n\t/** Release: replaces the selected (or given) blends with their keys (and a replaced spine as a path). */\n\treleaseBlend(options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst groups = this.blendTargets(options.ids);\n\t\tif (!groups.length) return [];\n\t\tconst released: string[] = [];\n\t\tthis.change('Release Blend', () => {\n\t\t\tfor (const g of groups) {\n\t\t\t\tconst nodes = releaseVectorBlend(g, () => this.newId());\n\t\t\t\treleased.push(...nodes.map((n) => n.id));\n\t\t\t\tthis.setDoc(spliceTree(this.doc.nodes, g.id, nodes));\n\t\t\t}\n\t\t\tthis.selection = released;\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn released;\n\t}\n\n\t/**\n\t * Replace Spine: the selected blend's steps follow the selected path instead of the straight\n\t * lines between its keys. The path's first subpath becomes the spine (the path is removed)\n\t * and the keys move onto it, spread evenly by arc length.\n\t */\n\treplaceBlendSpine(options: { ids?: readonly string[] } = {}): VectorActionResult {\n\t\tconst roots = options.ids ?? this.selectedRoots();\n\t\tconst groups = this.blendTargets(roots);\n\t\tconst paths = roots.map((id) => this.getNode(id)).filter((n): n is VectorPathNode => n?.type === 'path' && !n.blendStep && !isVectorBlendGroup(locateVectorNode(this.doc.nodes, n.id)?.parent));\n\t\tif (groups.length !== 1 || paths.length !== 1) return { ok: false, message: 'Select one blend and one path to be its spine.' };\n\t\tconst group = groups[0];\n\t\tconst path = paths[0];\n\t\tconst first = path.subpaths.find((sp) => sp.anchors.length >= 2);\n\t\tif (!first) return { ok: false, message: 'The spine needs a path with at least two points.' };\n\t\t// The path's first subpath in the blend group's own space.\n\t\tconst m = multiplyMatrices(this.worldOf(path.id), invertMatrix(this.worldOf(group.id)));\n\t\tconst spine = transformVectorNode<VectorPathNode>({ id: '', type: 'path', subpaths: [first] }, m).subpaths[0];\n\t\tthis.change('Replace Spine', () => {\n\t\t\tconst withSpine: VectorGroupNode = { ...group, blend: { ...group.blend, spine } };\n\t\t\tconst targets = vectorBlendKeyTargets(withSpine);\n\t\t\tlet k = 0;\n\t\t\tconst children = withSpine.children.map((c) => {\n\t\t\t\tif (c.blendStep) return c;\n\t\t\t\tconst target = targets[k++];\n\t\t\t\tconst b = vectorNodeBounds(c);\n\t\t\t\tif (!target || !b) return c;\n\t\t\t\tconst dx = target[0] - (b.x + b.width / 2);\n\t\t\t\tconst dy = target[1] - (b.y + b.height / 2);\n\t\t\t\treturn Math.hypot(dx, dy) > 1e-9 ? transformVectorNode(c, translateMatrix(dx, dy)) : c;\n\t\t\t});\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === path.id ? null : n.id === group.id ? { ...withSpine, children } : n)));\n\t\t\tthis.selection = [group.id];\n\t\t\tthis.anchors = [];\n\t\t\tif (this.editingId === path.id) this.editingId = null;\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\tprivate shapeBuilderMap(ids?: readonly string[]): VectorShapeBuilderMap | null {\n\t\tconst roots = ids ?? this.selectedRoots();\n\t\tconst key = roots.join('\\u0000');\n\t\tif (this.builderCache?.doc === this.doc && this.builderCache.key === key) return this.builderCache.map;\n\t\t// The Shape Builder edits the shapes it takes area from: their stored geometry.\n\t\tconst entries = roots.length ? vectorPathEntries(this.doc, roots, { expand: false }) : [];\n\t\tconst map = entries.length ? vectorShapeBuilderMap(entries) : null;\n\t\tthis.builderCache = { doc: this.doc, key, map };\n\t\treturn map;\n\t}\n\n\t/** The faces the Shape Builder offers for the selected (or given) paths (document units). */\n\tgetShapeBuilderFaces(options: { ids?: readonly string[] } = {}): VectorShapeBuilderFace[] {\n\t\tconst m = this.shapeBuilderMap(options.ids);\n\t\tif (!m) return [];\n\t\treturn m.map.faces.map((f, index) => {\n\t\t\tconst p = m.map.faceInterior(index);\n\t\t\treturn {\n\t\t\t\tindex,\n\t\t\t\td: bezierPathToSvg(f.path, { precision: 3 }),\n\t\t\t\tarea: f.area,\n\t\t\t\tsources: m.map.faceSources(index).map((i) => m.entries[i].node.id),\n\t\t\t\tpoint: p ? { x: p[0], y: p[1] } : null\n\t\t\t};\n\t\t});\n\t}\n\n\t/** Pick distance of the Shape Builder's erase for line pieces (document units). */\n\tprivate builderLineTolerance(): number {\n\t\treturn Math.max(this.hitRadius, 4) / this.viewport.zoom;\n\t}\n\n\t/**\n\t * Shape Builder gesture along `points` (document units; one point is a click) over the faces\n\t * the selected (or given) paths make. Merges the faces touched into one shape styled like the\n\t * top-most shape over the first face (the current fill for a face only outlines enclose);\n\t * with `erase` deletes them, the pieces of unfilled open paths and the edges of other outlines\n\t * (cut where paths meet) within `tolerance`: a deleted edge opens a closed subpath there,\n\t * splits an open one or takes a subpath it runs all the way round, and edges bounding a face\n\t * deleted in the same gesture go with the face. Every shape covering a touched face loses\n\t * that area; paths keep their own transforms. One undo step.\n\t */\n\tshapeBuilder(points: readonly Vec2[], options: { ids?: readonly string[]; erase?: boolean; tolerance?: number } = {}): VectorShapeBuilderResult {\n\t\tconst none = (message: string): VectorShapeBuilderResult => ({ ok: false, message, merged: null, faces: 0, lines: 0, edges: 0 });\n\t\tif (!points.length || !points.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y))) return none('Give the points of the gesture.');\n\t\tconst m = this.shapeBuilderMap(options.ids);\n\t\tif (!m) return none('Select paths that make faces.');\n\t\tconst erase = !!options.erase;\n\t\tconst tol = erase ? (options.tolerance ?? this.builderLineTolerance()) : null;\n\t\tconst touched: VectorBuilderTouched = { faces: [], lines: [], edges: [] };\n\t\tconst size = m.map.faces.reduce((s, f) => Math.max(s, f.bounds.maxX - f.bounds.minX, f.bounds.maxY - f.bounds.minY), 1);\n\t\tfor (const p of samplePolyline(points, Math.max(size / 2000, 0.25 / this.viewport.zoom))) addBuilderHit(touched, vectorShapeBuilderHit(m, p, tol));\n\t\tif (!touched.faces.length && !touched.lines.length && !touched.edges.length) return none('There is no face under the pointer.');\n\t\treturn this.applyShapeBuilder(m, touched, erase);\n\t}\n\n\tprivate applyShapeBuilder(m: VectorShapeBuilderMap, touched: VectorBuilderTouched, erase: boolean): VectorShapeBuilderResult {\n\t\tconst plan = planShapeBuilder(m, touched, erase);\n\t\tlet mergedId: string | null = null;\n\t\tconst created = new Map<string, string[]>();\n\t\tthis.change(erase ? 'Shape Builder Delete' : 'Shape Builder', () => {\n\t\t\tlet nodes = this.doc.nodes;\n\t\t\tm.entries.forEach((entry, i) => {\n\t\t\t\tconst node = entry.node;\n\t\t\t\tconst toLocal = (path: BezierPath) => transformBezierPath(path, invertMatrix(multiplyMatrices(nodeMatrix(node), entry.parent)));\n\t\t\t\tconst list: VectorNode[] = [];\n\t\t\t\tconst r = plan.replace.get(i);\n\t\t\t\tif (r === undefined) list.push(node);\n\t\t\t\telse if (r?.kind === 'fill') list.push({ ...node, fillRule: 'nonzero', subpaths: bezierToSubpaths(toLocal(r.path)) });\n\t\t\t\telse if (r?.kind === 'outline') list.push({ ...node, subpaths: bezierToSubpaths(toLocal(r.path)) });\n\t\t\t\telse if (r?.kind === 'pieces') {\n\t\t\t\t\tr.paths.forEach((path, k) =>\n\t\t\t\t\t\tlist.push({ ...node, id: k === 0 ? node.id : this.newId(), subpaths: bezierToSubpaths(toLocal(path)).map((sp) => ({ ...sp, closed: false })) })\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (plan.merged && plan.merged.style === i) {\n\t\t\t\t\tmergedId = this.newId();\n\t\t\t\t\tconst merged: VectorPathNode = {\n\t\t\t\t\t\t...vectorResultStyle(node, 'fill'),\n\t\t\t\t\t\ttype: 'path',\n\t\t\t\t\t\tid: mergedId,\n\t\t\t\t\t\tname: 'Shape',\n\t\t\t\t\t\t...(node.transform ? { transform: node.transform } : {}),\n\t\t\t\t\t\tsubpaths: bezierToSubpaths(toLocal(plan.merged.path))\n\t\t\t\t\t};\n\t\t\t\t\tif (plan.merged.currentFill || !hasPaint(merged.fill)) merged.fill = hasPaint(this.defaults.fill) ? this.defaults.fill! : '#808080';\n\t\t\t\t\tlist.push(merged);\n\t\t\t\t}\n\t\t\t\tif (r === undefined && list.length === 1) return;\n\t\t\t\tcreated.set(node.id, list.map((n) => n.id));\n\t\t\t\tnodes = spliceTree(nodes, node.id, list);\n\t\t\t});\n\t\t\tthis.setDoc(nodes);\n\t\t\t// The shapes stay selected so the next gesture works on them too.\n\t\t\tconst selection = this.selection.flatMap((id) => created.get(id) ?? [id]);\n\t\t\tif (mergedId && !selection.includes(mergedId)) selection.push(mergedId);\n\t\t\tthis.selection = selection.filter((id, k) => !!this.getNode(id) && selection.indexOf(id) === k);\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\tthis.builderCache = null;\n\t\treturn { ok: true, merged: mergedId, faces: touched.faces.length, lines: touched.lines.length, edges: erase ? vectorBuilderErasedEdges(m, touched).length : 0 };\n\t}\n\n\t/** The paint bucket's map of the visible outlines, cached per document and gap. */\n\tprivate fillMap(gap: number): PlanarMap | null {\n\t\tif (this.fillCache?.doc === this.doc && this.fillCache.gap === gap) return this.fillCache.map;\n\t\tconst map = vectorFillMap(this.doc, gap);\n\t\tthis.fillCache = { doc: this.doc, gap, map };\n\t\treturn map;\n\t}\n\n\t/**\n\t * Paint bucket: the region the visible outlines enclose around `point` (document units),\n\t * crossings split and islands left as holes, becomes a new path filled with the current fill\n\t * default, placed just below the selection (or on top when nothing is selected). `gap`\n\t * (default `editing.fillGap`) closes gaps up to that size between loose ends. Returns the new\n\t * path's id, or null when nothing encloses the point.\n\t */\n\tfillRegion(point: Vec2, options: { gap?: number } = {}): string | null {\n\t\tif (!Number.isFinite(point.x) || !Number.isFinite(point.y)) return null;\n\t\tconst region = this.fillMap(options.gap ?? this.editing.fillGap)?.regionAt([point.x, point.y]);\n\t\tif (!region?.length) return null;\n\t\t// Below the lowest selected node, in its parent.\n\t\tlet below: string | null = null;\n\t\tconst roots = new Set(this.selectedRoots());\n\t\twalkVectorNodes(this.doc.nodes, (n) => {\n\t\t\tif (!below && roots.has(n.id)) below = n.id;\n\t\t});\n\t\tconst subpaths = bezierToSubpaths(below ? this.intoParentSpace(below, region) : region);\n\t\tif (!subpaths.length) return null;\n\t\tconst id = this.newId();\n\t\tconst node: VectorPathNode = { id, type: 'path', name: 'Fill', ...this.styleFor(false), stroke: null, fillRule: 'nonzero', subpaths };\n\t\tif (!hasPaint(node.fill)) node.fill = '#808080';\n\t\tconst target = below as string | null;\n\t\tthis.change('Fill Region', () => {\n\t\t\tif (target) {\n\t\t\t\tconst current = this.getNode(target)!;\n\t\t\t\tthis.setDoc(spliceTree(this.doc.nodes, target, [node, current]));\n\t\t\t} else this.setDoc([...this.doc.nodes, node]);\n\t\t\tif (!target) this.selection = [id];\n\t\t\tthis.anchors = [];\n\t\t\tthis.editingId = null;\n\t\t});\n\t\treturn id;\n\t}\n\n\t/** Updates the Shape Builder / paint bucket highlight for the pointer at `p`; true when it changed. */\n\tprivate hoverBuilder(p: Vec2, alt: boolean): boolean {\n\t\tif (this.tool === 'paintBucket') {\n\t\t\tconst region = this.fillMap(this.editing.fillGap)?.regionAt([p.x, p.y]) ?? null;\n\t\t\tconst key = region ? bezierPathToSvg(region, { precision: 2 }) : null;\n\t\t\tif (key === this.builderHoverKey) return false;\n\t\t\tthis.builderHoverKey = key;\n\t\t\tthis.builderPreview = region ? { mode: 'fill', faces: [region], lines: [], trail: null } : null;\n\t\t\treturn true;\n\t\t}\n\t\tconst m = this.selectedRoots().length ? this.shapeBuilderMap() : null;\n\t\tconst hit = m ? vectorShapeBuilderHit(m, p, alt ? this.builderLineTolerance() : null) : null;\n\t\tconst key = hit ? `${alt ? 'erase' : 'merge'}:${JSON.stringify(hit)}` : null;\n\t\tif (key === this.builderHoverKey) return false;\n\t\tthis.builderHoverKey = key;\n\t\tconst touched: VectorBuilderTouched = { faces: [], lines: [], edges: [] };\n\t\taddBuilderHit(touched, hit);\n\t\tthis.builderPreview = hit && m ? this.builderPreviewOf(m, touched, alt, null) : null;\n\t\treturn true;\n\t}\n\n\tprivate builderPreviewOf(m: VectorShapeBuilderMap, touched: VectorBuilderTouched, erase: boolean, trail: Vec2[] | null): VectorBuilderPreview {\n\t\tconst faces = m.map.faces;\n\t\treturn {\n\t\t\tmode: erase ? 'erase' : 'merge',\n\t\t\tfaces: touched.faces.map((f) => faces[f].path),\n\t\t\t// Edges bounding a face touched too go with the face, so they are not shown apart.\n\t\t\tlines: [...touched.lines.map((k) => m.pieces[k].path), ...(erase ? vectorBuilderErasedEdges(m, touched).map((k) => m.edges[k].path) : [])],\n\t\t\ttrail\n\t\t};\n\t}\n\n\tprivate downBuilder(p: Vec2, input: VectorPointerInput) {\n\t\tthis.builderHoverKey = null;\n\t\tif (this.tool === 'paintBucket') {\n\t\t\tthis.fillRegion(p);\n\t\t\tthis.builderPreview = null;\n\t\t\tthis.hoverBuilder(p, false);\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst m = this.selectedRoots().length ? this.shapeBuilderMap() : null;\n\t\tif (!m) return;\n\t\tconst erase = !!input.alt;\n\t\tconst touched: VectorBuilderTouched = { faces: [], lines: [], edges: [] };\n\t\taddBuilderHit(touched, vectorShapeBuilderHit(m, p, erase ? this.builderLineTolerance() : null));\n\t\tthis.drag = { kind: 'builder', erase, points: [p], touched, map: m };\n\t\tthis.interaction = { type: 'builder', mode: erase ? 'erase' : 'merge', points: [p] };\n\t\tthis.builderPreview = this.builderPreviewOf(m, touched, erase, [p]);\n\t\tthis.notify();\n\t}\n\n\tprivate moveBuilder(drag: Extract<Drag, { kind: 'builder' }>, p: Vec2) {\n\t\tconst last = drag.points[drag.points.length - 1];\n\t\tif (Math.hypot(p.x - last.x, p.y - last.y) * this.viewport.zoom < 1) return;\n\t\tconst tol = drag.erase ? this.builderLineTolerance() : null;\n\t\tfor (const q of samplePolyline([last, p], 2 / this.viewport.zoom)) addBuilderHit(drag.touched, vectorShapeBuilderHit(drag.map, q, tol));\n\t\tdrag.points.push(p);\n\t\tthis.interaction = { type: 'builder', mode: drag.erase ? 'erase' : 'merge', points: drag.points.slice() };\n\t\tthis.builderPreview = this.builderPreviewOf(drag.map, drag.touched, drag.erase, drag.points.slice());\n\t\tthis.notify();\n\t}\n\n\tprivate upBuilder(drag: Extract<Drag, { kind: 'builder' }>) {\n\t\tthis.builderPreview = null;\n\t\tthis.builderHoverKey = null;\n\t\tif (!drag.touched.faces.length && !drag.touched.lines.length && !drag.touched.edges.length) {\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tthis.applyShapeBuilder(drag.map, drag.touched, drag.erase);\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Anchor editing\n\t// -------------------------------------------------------------------------\n\n\tprivate updateAnchors(doc: VectorDocument, refs: readonly VectorAnchorRef[], fn: (a: VectorAnchor, ref: VectorAnchorRef) => VectorAnchor): VectorDocument {\n\t\tconst byNode = new Map<string, VectorAnchorRef[]>();\n\t\tfor (const r of refs) byNode.set(r.nodeId, [...(byNode.get(r.nodeId) ?? []), r]);\n\t\tconst nodes = mapTree(doc.nodes, (n) => {\n\t\t\tconst list = byNode.get(n.id);\n\t\t\tif (!list || n.type !== 'path' || n.locked) return n;\n\t\t\tconst subpaths = n.subpaths.map((sp, s) => {\n\t\t\t\tconst hits = list.filter((r) => r.subpath === s);\n\t\t\t\tif (!hits.length) return sp;\n\t\t\t\treturn { closed: sp.closed, anchors: sp.anchors.map((a, i) => (hits.some((r) => r.index === i) ? fn(a, { nodeId: n.id, subpath: s, index: i }) : a)) };\n\t\t\t});\n\t\t\treturn { ...n, subpaths };\n\t\t});\n\t\treturn nodes === doc.nodes ? doc : { ...doc, nodes };\n\t}\n\n\t/** Moves anchors by a document-space offset (converted into each node's own space). */\n\tprivate movedAnchors(doc: VectorDocument, refs: readonly VectorAnchorRef[], dx: number, dy: number): VectorDocument {\n\t\tconst local = new Map<string, Vec2>();\n\t\tfor (const r of refs) if (!local.has(r.nodeId)) local.set(r.nodeId, applyMatrixLinear(invertMatrix(this.worldOf(r.nodeId, doc)), { x: dx, y: dy }));\n\t\treturn this.updateAnchors(doc, refs, (a, ref) => {\n\t\t\tconst d = local.get(ref.nodeId)!;\n\t\t\treturn translateAnchor(a, d.x, d.y);\n\t\t});\n\t}\n\n\t/**\n\t * Moves one anchor to a document position. Its handles follow; with `curvature` (default:\n\t * the `curvatureMoves` setting) its two segments bend instead, as the Curvature tool does.\n\t */\n\tmoveAnchor(ref: VectorAnchorRef, to: Vec2, options: { curvature?: boolean } = {}): VectorActionResult {\n\t\tconst a = getAnchorWorld(this.doc, ref);\n\t\tif (!a) return { ok: false, message: 'There is no such anchor.' };\n\t\tconst curvature = options.curvature ?? this.editing.curvatureMoves;\n\t\tthis.change('Move Point', () => {\n\t\t\tthis.doc = curvature ? this.curvatureMoved(this.doc, ref, to) : this.movedAnchors(this.doc, [ref], to.x - a.x, to.y - a.y);\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** `doc` with anchor `ref` moved to document point `to` by a Curvature-tool move. */\n\tprivate curvatureMoved(doc: VectorDocument, ref: VectorAnchorRef, to: Vec2): VectorDocument {\n\t\tconst node = findVectorNode(doc.nodes, ref.nodeId);\n\t\tif (node?.type !== 'path' || node.locked || !node.subpaths[ref.subpath]) return doc;\n\t\tconst q = this.toLocal(ref.nodeId, to, doc);\n\t\tconst subpaths = node.subpaths.map((sp, i) => (i === ref.subpath ? curvatureMoveAnchor(sp, ref.index, q) : sp));\n\t\tconst nodes = mapTree(doc.nodes, (n) => (n.id === ref.nodeId ? { ...node, subpaths } : n));\n\t\treturn { ...doc, nodes };\n\t}\n\n\t/** `doc` with segment `ref` bent through document point `p` at parameter `t`. */\n\tprivate bent(doc: VectorDocument, ref: VectorSegmentRef, t: number, p: Vec2): VectorDocument {\n\t\tconst node = findVectorNode(doc.nodes, ref.nodeId);\n\t\tif (node?.type !== 'path' || node.locked || !node.subpaths[ref.subpath]) return doc;\n\t\tconst q = this.toLocal(ref.nodeId, p, doc);\n\t\tconst subpaths = node.subpaths.map((sp, i) => (i === ref.subpath ? bendSubpathSegment(sp, ref.segment, t, q) : sp));\n\t\treturn { ...doc, nodes: mapTree(doc.nodes, (n) => (n.id === ref.nodeId ? { ...node, subpaths } : n)) };\n\t}\n\n\t/**\n\t * Bends a segment so the curve passes through document point `to` at parameter `t`\n\t * (default 0.5, clamped to 0.05–0.95); see `bendSegment` in `pathEdit`.\n\t */\n\tbendSegment(ref: VectorSegmentRef, to: Vec2, t = 0.5): VectorActionResult {\n\t\tconst node = this.getNode(ref.nodeId);\n\t\tconst sp = node?.type === 'path' ? node.subpaths[ref.subpath] : undefined;\n\t\tif (!sp || ref.segment < 0 || ref.segment >= (sp.closed ? sp.anchors.length : sp.anchors.length - 1) || sp.anchors.length < 2) return { ok: false, message: 'There is no such segment.' };\n\t\tif (node!.locked) return { ok: false, message: 'The path is locked.' };\n\t\tthis.change('Bend Segment', () => {\n\t\t\tthis.doc = this.bent(this.doc, ref, t, to);\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Moves one handle to a document position (see `moveAnchorHandle` for how the opposite handle follows). */\n\tmoveHandle(ref: VectorHandleRef, to: Vec2, options: { breakHandles?: boolean } = {}): VectorActionResult {\n\t\tif (!getAnchor(this.doc, ref)) return { ok: false, message: 'There is no such anchor.' };\n\t\tconst local = this.toLocal(ref.nodeId, to);\n\t\tthis.change('Move Handle', () => {\n\t\t\tthis.doc = this.updateAnchors(this.doc, [ref], (a) => moveAnchorHandle(a, ref.handle, local, options.breakHandles));\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Converts the selected (or given) anchors between corner, smooth and symmetric. */\n\tconvertAnchors(type: VectorAnchorType, refs: readonly VectorAnchorRef[] = this.anchors): VectorActionResult {\n\t\tif (!refs.length) return { ok: false, message: 'Select points to convert.' };\n\t\tthis.change(type === 'corner' ? 'Make Corner' : 'Make Smooth', () => {\n\t\t\tconst nodes = mapTree(this.doc.nodes, (n) => {\n\t\t\t\tif (n.type !== 'path') return n;\n\t\t\t\tconst mine = refs.filter((r) => r.nodeId === n.id);\n\t\t\t\tif (!mine.length) return n;\n\t\t\t\tconst subpaths = n.subpaths.map((sp, s) => mine.filter((r) => r.subpath === s).reduce((acc, r) => convertAnchor(acc, r.index, type), sp));\n\t\t\t\treturn { ...n, subpaths };\n\t\t\t});\n\t\t\tthis.setDoc(nodes);\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Adds an anchor on the nearest segment of `nodeId` to document point `point` (shape unchanged). */\n\tinsertAnchorAt(nodeId: string, point: Vec2): VectorAnchorRef | null {\n\t\tconst node = this.getNode(nodeId);\n\t\tif (node?.type !== 'path') return null;\n\t\tconst hit = nearestSegment(node, this.toLocal(nodeId, point));\n\t\tif (!hit) return null;\n\t\tconst { subpath, index } = insertAnchor(node.subpaths[hit.subpath], hit.segment, hit.t);\n\t\tconst ref = { nodeId, subpath: hit.subpath, index };\n\t\tthis.change('Add Point', () => {\n\t\t\tthis.replaceNode(nodeId, { ...node, subpaths: node.subpaths.map((sp, i) => (i === hit.subpath ? subpath : sp)) });\n\t\t\tthis.editingId = nodeId;\n\t\t\tthis.anchors = [ref];\n\t\t});\n\t\treturn ref;\n\t}\n\n\t/**\n\t * Deletes the selected (or given) anchors; subpaths left with one anchor and empty paths go\n\t * too. With `keepShape` (default: the `keepShapeOnDelete` setting, on) the neighbours'\n\t * handles are refitted so the path keeps its shape; off, the neighbours are joined as they are.\n\t */\n\tdeleteAnchors(refs: readonly VectorAnchorRef[] = this.anchors, options: { keepShape?: boolean } = {}): VectorActionResult {\n\t\tif (!refs.length) return { ok: false, message: 'Select points to delete.' };\n\t\tconst keepShape = options.keepShape ?? this.editing.keepShapeOnDelete;\n\t\tthis.change('Delete Points', () => {\n\t\t\tconst nodes = mapTree(this.doc.nodes, (n) => {\n\t\t\t\tif (n.type !== 'path' || n.locked) return n;\n\t\t\t\tconst mine = refs.filter((r) => r.nodeId === n.id);\n\t\t\t\tif (!mine.length) return n;\n\t\t\t\tconst subpaths = n.subpaths\n\t\t\t\t\t.map((sp, s) => {\n\t\t\t\t\t\tconst indices = mine.filter((r) => r.subpath === s).map((r) => r.index);\n\t\t\t\t\t\tif (!indices.length) return sp;\n\t\t\t\t\t\treturn keepShape ? removeAnchorsKeepingShape(sp, indices) : removeAnchors(sp, indices);\n\t\t\t\t\t})\n\t\t\t\t\t.filter((sp) => sp.anchors.length > 1);\n\t\t\t\treturn subpaths.length ? { ...n, subpaths } : null;\n\t\t\t});\n\t\t\tthis.setDoc(nodes);\n\t\t\tthis.anchors = [];\n\t\t\tthis.selection = this.selection.filter((id) => this.getNode(id));\n\t\t\tif (this.editingId && !this.getNode(this.editingId)) this.editingId = null;\n\t\t\tif (this.pen && !this.getNode(this.pen.nodeId)) this.pen = null;\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Opens or closes the subpaths that contain selected anchors (or all subpaths of `nodeId`). */\n\tsetClosed(closed: boolean, nodeId?: string): VectorActionResult {\n\t\tconst targets = nodeId\n\t\t\t? (() => {\n\t\t\t\t\tconst n = this.getNode(nodeId);\n\t\t\t\t\treturn n?.type === 'path' ? n.subpaths.map((_, s) => ({ nodeId, subpath: s })) : [];\n\t\t\t\t})()\n\t\t\t: this.anchors.map((r) => ({ nodeId: r.nodeId, subpath: r.subpath }));\n\t\tif (!targets.length) return { ok: false, message: 'Select a path or points.' };\n\t\tthis.change(closed ? 'Close Path' : 'Open Path', () => {\n\t\t\tthis.setDoc(\n\t\t\t\tmapTree(this.doc.nodes, (n) =>\n\t\t\t\t\tn.type === 'path' && targets.some((t) => t.nodeId === n.id)\n\t\t\t\t\t\t? { ...n, subpaths: n.subpaths.map((sp, s) => (targets.some((t) => t.nodeId === n.id && t.subpath === s) ? { ...sp, closed } : sp)) }\n\t\t\t\t\t\t: n\n\t\t\t\t)\n\t\t\t);\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Reverses the direction of every subpath of the selected paths. */\n\treversePaths(ids: readonly string[] = this.selection): VectorActionResult {\n\t\tconst set = new Set(ids.flatMap((id) => (this.getNode(id) ? collectPathNodes(this.getNode(id)!) : [])).map((p) => p.id));\n\t\tif (!set.size) return { ok: false, message: 'Select a path.' };\n\t\tthis.change('Reverse Path', () => {\n\t\t\tthis.setDoc(\n\t\t\t\tmapTree(this.doc.nodes, (n) =>\n\t\t\t\t\tn.type === 'path' && set.has(n.id)\n\t\t\t\t\t\t? { ...n, subpaths: n.subpaths.map(reverseSubpath), ...(n.strokeWidthProfile?.length ? { strokeWidthProfile: reverseWidthProfile(n.strokeWidthProfile) } : {}) }\n\t\t\t\t\t\t: n\n\t\t\t\t)\n\t\t\t);\n\t\t\tthis.anchors = [];\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Path clean-up, Scissors, Knife and Eraser\n\t// -------------------------------------------------------------------------\n\n\t/** Unlocked path nodes in the given roots (default: the paths being edited, else the selection). */\n\tprivate pathTargets(ids?: readonly string[]): VectorPathNode[] {\n\t\tconst roots = ids ?? (this.tool === 'direct' && this.anchors.length ? [...new Set(this.anchors.map((r) => r.nodeId))] : this.selection);\n\t\tconst out = new Map<string, VectorPathNode>();\n\t\tfor (const id of roots) {\n\t\t\tconst node = this.getNode(id);\n\t\t\tif (node && !node.locked) for (const p of collectPathNodes(node)) if (!p.locked) out.set(p.id, p);\n\t\t}\n\t\t// Document order, bottom to top.\n\t\tconst order: string[] = [];\n\t\twalkVectorNodes(this.doc.nodes, (n) => {\n\t\t\tif (out.has(n.id)) order.push(n.id);\n\t\t});\n\t\treturn order.map((id) => out.get(id)!);\n\t}\n\n\t/** Subpaths of node `from` mapped into node `to`'s own space. */\n\tprivate subpathsIn(from: VectorPathNode, to: string): VectorSubpath[] {\n\t\tconst m = multiplyMatrices(this.worldOf(from.id), invertMatrix(this.worldOf(to)));\n\t\tif (isRoot(m)) return from.subpaths;\n\t\tconst map = (p: Vec2) => applyMatrix(m, p);\n\t\treturn from.subpaths.map((sp) => ({\n\t\t\tclosed: sp.closed,\n\t\t\tanchors: sp.anchors.map((a) => ({ ...map(a), ...(a.in ? { in: map(a.in) } : {}), ...(a.out ? { out: map(a.out) } : {}), ...(a.type ? { type: a.type } : {}), ...(a.corner ? { corner: { ...a.corner } } : {}) }))\n\t\t}));\n\t}\n\n\t/**\n\t * Replaces a path with pieces: each entry becomes one path (the first keeps the node's id and\n\t * place, the others go just above it with the same style). No pieces removes the path.\n\t * Returns the ids of the paths now standing in for it.\n\t */\n\tprivate replaceWithPieces(node: VectorPathNode, pieces: readonly VectorSubpath[][]): string[] {\n\t\tconst nodes = pieces.filter((p) => p.length).map((subpaths, i) => ({ ...node, id: i === 0 ? node.id : this.newId(), subpaths }) as VectorPathNode);\n\t\tthis.setDoc(spliceTree(this.doc.nodes, node.id, nodes));\n\t\treturn nodes.map((n) => n.id);\n\t}\n\n\t/** Clears anchor selections, editing and pen state that no longer point at anything. */\n\tprivate dropStaleRefs() {\n\t\tthis.selection = this.selection.filter((id) => this.getNode(id));\n\t\tthis.anchors = this.anchors.filter((r) => getAnchor(this.doc, r));\n\t\tif (this.editingId && !this.getNode(this.editingId)) this.editingId = null;\n\t\tif (this.pen && !this.getNode(this.pen.nodeId)) this.pen = null;\n\t}\n\n\t/**\n\t * Join: the open subpaths of the selected (or given) paths become one open path, joined\n\t * nearest endpoints first (ends within `tolerance` document units merge into one anchor,\n\t * others get a straight segment); a single open subpath is closed. The result replaces the\n\t * lowest path, in its space and style. Default tolerance: the pick distance.\n\t */\n\tjoinPaths(options: { ids?: readonly string[]; tolerance?: number } = {}): VectorActionResult {\n\t\tconst paths = this.pathTargets(options.ids);\n\t\tif (!paths.length) return { ok: false, message: 'Select paths to join.' };\n\t\tconst base = paths[0];\n\t\tconst all = paths.flatMap((p) => this.subpathsIn(p, base.id));\n\t\tif (!all.some((sp) => !sp.closed && sp.anchors.length > 1)) return { ok: false, message: 'There are no open paths to join.' };\n\t\tconst subpaths = joinSubpaths(all, options.tolerance ?? this.tolerance());\n\t\tconst drop = new Set(paths.slice(1).map((p) => p.id));\n\t\tthis.change('Join', () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.id === base.id ? { ...base, subpaths } : drop.has(n.id) ? null : n)));\n\t\t\tthis.selection = [base.id];\n\t\t\tthis.anchors = [];\n\t\t\tif (this.editingId && drop.has(this.editingId)) this.editingId = base.id;\n\t\t\tthis.dropStaleRefs();\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Average: moves the selected (or given) anchors onto their average position along `axis`, in document space. */\n\taverageAnchors(axis: VectorAverageAxis = 'both', refs: readonly VectorAnchorRef[] = this.anchors): VectorActionResult {\n\t\tconst points = refs.map((r) => ({ ref: r, at: getAnchorWorld(this.doc, r) })).filter((p): p is { ref: VectorAnchorRef; at: NonNullable<ReturnType<typeof getAnchorWorld>> } => !!p.at);\n\t\tif (points.length < 2) return { ok: false, message: 'Select at least two points to average.' };\n\t\tconst cx = points.reduce((s, p) => s + p.at.x, 0) / points.length;\n\t\tconst cy = points.reduce((s, p) => s + p.at.y, 0) / points.length;\n\t\tthis.change('Average', () => {\n\t\t\tfor (const { ref, at } of points) {\n\t\t\t\tconst dx = axis === 'horizontal' ? 0 : cx - at.x;\n\t\t\t\tconst dy = axis === 'vertical' ? 0 : cy - at.y;\n\t\t\t\tif (dx || dy) this.doc = this.movedAnchors(this.doc, [ref], dx, dy);\n\t\t\t}\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/** Applies `fn` to every subpath of the target paths as one undo step. */\n\tprivate editSubpaths(label: string, ids: readonly string[] | undefined, fn: (sp: VectorSubpath, node: VectorPathNode, index: number) => VectorSubpath): VectorActionResult {\n\t\tconst paths = this.pathTargets(ids);\n\t\tif (!paths.length) return { ok: false, message: 'Select a path.' };\n\t\tconst set = new Set(paths.map((p) => p.id));\n\t\tthis.change(label, () => {\n\t\t\tthis.setDoc(mapTree(this.doc.nodes, (n) => (n.type === 'path' && set.has(n.id) ? { ...n, subpaths: n.subpaths.map((sp, i) => fn(sp, n, i)) } : n)));\n\t\t\tthis.dropStaleRefs();\n\t\t});\n\t\treturn { ok: true };\n\t}\n\n\t/**\n\t * Remove Redundant Points: drops anchors of the selected (or given) paths that can go\n\t * without moving the outline more than `tolerance` document units (default 0.5).\n\t */\n\tremoveRedundantPoints(options: { ids?: readonly string[]; tolerance?: number } = {}): VectorActionResult {\n\t\tconst tolerance = options.tolerance ?? 0.5;\n\t\treturn this.editSubpaths('Remove Redundant Points', options.ids, (sp) => removeRedundantSubpathPoints(sp, tolerance));\n\t}\n\n\t/**\n\t * Smooth: pulls handles towards smooth tangents by `amount` (0–1, default 1). With the direct\n\t * tool and points selected, only those points change.\n\t */\n\tsmoothPaths(options: { ids?: readonly string[]; amount?: number; refs?: readonly VectorAnchorRef[] } = {}): VectorActionResult {\n\t\tconst refs = options.refs ?? (!options.ids && this.tool === 'direct' && this.anchors.length ? this.anchors : null);\n\t\tconst ids = options.ids ?? (refs ? [...new Set(refs.map((r) => r.nodeId))] : undefined);\n\t\treturn this.editSubpaths('Smooth', ids, (sp, node, i) => {\n\t\t\tconst only = refs ? new Set(refs.filter((r) => r.nodeId === node.id && r.subpath === i).map((r) => r.index)) : undefined;\n\t\t\treturn only && !only.size ? sp : smoothSubpath(sp, options.amount ?? 1, only);\n\t\t});\n\t}\n\n\t/** Splits a cut subpath into pieces: separate paths for a one-subpath node, subpaths in place for a compound one. */\n\tprivate applyCuts(node: VectorPathNode, cut: (sp: VectorSubpath, index: number) => VectorSubpath[] | null): string[] | null {\n\t\tconst results = node.subpaths.map((sp, i) => cut(sp, i));\n\t\tif (results.every((r) => !r)) return null;\n\t\tconst subpaths = node.subpaths.flatMap((sp, i) => results[i] ?? [sp]);\n\t\tif (node.subpaths.length === 1) return this.replaceWithPieces(node, subpaths.map((sp) => [sp]));\n\t\tthis.replaceNode(node.id, { ...node, subpaths });\n\t\treturn [node.id];\n\t}\n\n\t/**\n\t * Scissors: cuts path `nodeId` at the outline point nearest document point `point` (at an\n\t * anchor when within the pick distance of one). A closed subpath opens there; an open one\n\t * splits in two (a one-subpath path becomes two paths). Returns the ids of the paths.\n\t */\n\tcutPathAt(nodeId: string, point: Vec2): string[] | null {\n\t\tconst node = this.getNode(nodeId);\n\t\tif (node?.type !== 'path' || node.locked) return null;\n\t\tconst local = this.toLocal(nodeId, point);\n\t\tconst hit = nearestSegment(node, local);\n\t\tif (!hit) return null;\n\t\tconst sp = node.subpaths[hit.subpath];\n\t\tconst n = sp.anchors.length;\n\t\tconst near = [hit.segment, (hit.segment + 1) % n].find((i) => Math.hypot(sp.anchors[i].x - hit.point.x, sp.anchors[i].y - hit.point.y) <= this.tolerance());\n\t\tif (!sp.closed && n > 1 && (near === 0 || near === n - 1) && !(hit.t > 1e-6 && hit.t < 1 - 1e-6)) return null;\n\t\tlet ids: string[] | null = null;\n\t\tthis.change('Cut Path', () => {\n\t\t\tids = this.applyCuts(node, (s, i) => (i !== hit.subpath ? null : near !== undefined ? cutSubpathAt(s, [near]) : splitSubpathAt(s, hit.segment, hit.t)));\n\t\t\tif (ids) {\n\t\t\t\tthis.selection = ids;\n\t\t\t\tthis.anchors = [];\n\t\t\t\tif (this.editingId === nodeId) this.editingId = null;\n\t\t\t}\n\t\t\tthis.dropStaleRefs();\n\t\t});\n\t\treturn ids;\n\t}\n\n\t/** Scissors on the selected (or given) anchors: each cuts its subpath there (see `cutPathAt`). */\n\tcutAtAnchors(refs: readonly VectorAnchorRef[] = this.anchors): VectorActionResult {\n\t\tconst byNode = new Map<string, VectorAnchorRef[]>();\n\t\tfor (const r of refs) if (getAnchor(this.doc, r)) byNode.set(r.nodeId, [...(byNode.get(r.nodeId) ?? []), r]);\n\t\tif (!byNode.size) return { ok: false, message: 'Select points to cut at.' };\n\t\tconst created: string[] = [];\n\t\tthis.change('Cut Path', () => {\n\t\t\tfor (const [id, list] of byNode) {\n\t\t\t\tconst node = this.getNode(id) as VectorPathNode;\n\t\t\t\tif (node.locked) continue;\n\t\t\t\tconst ids = this.applyCuts(node, (sp, i) => {\n\t\t\t\t\tconst at = list.filter((r) => r.subpath === i).map((r) => r.index);\n\t\t\t\t\tconst pieces = at.length ? cutSubpathAt(sp, at) : null;\n\t\t\t\t\treturn pieces && !(pieces.length === 1 && pieces[0].closed === sp.closed) ? pieces : null;\n\t\t\t\t});\n\t\t\t\tif (ids) created.push(...ids);\n\t\t\t}\n\t\t\tif (created.length) this.selection = created;\n\t\t\tthis.anchors = [];\n\t\t\tthis.dropStaleRefs();\n\t\t});\n\t\treturn created.length ? { ok: true } : { ok: false, message: 'Points at the ends of open paths do not cut.' };\n\t}\n\n\t/** Visible, unlocked paths a stroke over `points` can reach: in `ids`, the selection, or (with neither) any whose bounds it crosses. */\n\tprivate strokeTargets(points: readonly Vec2[], ids: readonly string[] | undefined, pad: number): VectorPathNode[] {\n\t\tconst blocked = new Set<string>();\n\t\twalkVectorNodes(this.doc.nodes, (n, parent) => {\n\t\t\tif (n.visible === false || n.locked || (parent && blocked.has(parent.id))) blocked.add(n.id);\n\t\t});\n\t\tconst visible = (p: VectorPathNode) => !blocked.has(p.id);\n\t\tif (ids?.length || this.selection.length) return this.pathTargets(ids ?? this.selection).filter(visible);\n\t\tconst box = polylineBounds(points.map(toPoint), pad);\n\t\tif (!box) return [];\n\t\tconst out: VectorPathNode[] = [];\n\t\twalkVectorNodes(this.doc.nodes, (n) => {\n\t\t\tif (n.type !== 'path' || !visible(n)) return;\n\t\t\tconst b = vectorNodeBounds(n, vectorParentMatrix(this.doc.nodes, n.id) ?? IDENTITY_MATRIX);\n\t\t\tif (b && boundsIntersect(b, box)) out.push(n);\n\t\t});\n\t\treturn out;\n\t}\n\n\t/** A stroke's points in node `id`'s own space. */\n\tprivate localPoints(id: string, points: readonly Vec2[]): Point[] {\n\t\tconst inv = invertMatrix(this.worldOf(id));\n\t\treturn points.map((p) => toPoint(applyMatrix(inv, p)));\n\t}\n\n\t/**\n\t * Knife: cuts closed paths along the polyline `points` (document space) wherever it crosses\n\t * them from edge to edge, with no gap between the pieces. It cuts the given paths, the\n\t * selection, or (with neither) every path it crosses. Returns the ids of the pieces (empty\n\t * when nothing was cut).\n\t */\n\tknife(points: readonly Vec2[], options: { ids?: readonly string[] } = {}): string[] {\n\t\tconst pts = simplifyPolyline(points.map(toPoint), 0.25 / this.viewport.zoom).map(([x, y]) => ({ x, y }));\n\t\tif (pts.length < 2) return [];\n\t\tconst plan: { node: VectorPathNode; pieces: VectorSubpath[][] }[] = [];\n\t\tfor (const node of this.strokeTargets(pts, options.ids, 0)) {\n\t\t\tconst closed = node.subpaths.filter((sp) => sp.anchors.length > 1);\n\t\t\tif (!closed.length || closed.some((sp) => !sp.closed)) continue;\n\t\t\tconst result = knifePath(subpathsToBezier(closed), node.fillRule ?? 'nonzero', this.localPoints(node.id, pts));\n\t\t\tif (result) plan.push({ node, pieces: result.map((path) => bezierToSubpaths(path)) });\n\t\t}\n\t\tif (!plan.length) return [];\n\t\tconst created: string[] = [];\n\t\tthis.change('Knife', () => {\n\t\t\tfor (const { node, pieces } of plan) created.push(...this.replaceWithPieces(node, pieces));\n\t\t\tthis.selection = created;\n\t\t\tthis.anchors = [];\n\t\t\tthis.dropStaleRefs();\n\t\t});\n\t\treturn created;\n\t}\n\n\t/**\n\t * Eraser: rubs out a round stroke of diameter `size` (default: the `eraserSize` setting)\n\t * along `points` (document space). Filled closed paths lose that area (pieces that come\n\t * apart become separate paths); outlines are cut where they pass under it. It erases from\n\t * the given paths, the selection, or (with neither) every path it reaches. Returns the ids\n\t * of the paths it changed or created.\n\t */\n\terase(points: readonly Vec2[], options: { ids?: readonly string[]; size?: number } = {}): string[] {\n\t\tconst width = options.size ?? this.editing.eraserSize;\n\t\tif (!(width > 0) || !points.length) return [];\n\t\tconst pts = simplifyPolyline(points.map(toPoint), Math.min(width * 0.05, 0.25 / this.viewport.zoom));\n\t\tconst region = eraserRegion(pts, width);\n\t\tif (!region.length) return [];\n\t\tconst plan: { node: VectorPathNode; pieces: VectorSubpath[][] }[] = [];\n\t\tconst rb = bezierPathBounds(region);\n\t\tconst reach = rb ? [{ x: rb.minX, y: rb.minY }, { x: rb.maxX, y: rb.maxY }] : points;\n\t\tfor (const node of this.strokeTargets(reach, options.ids, 0)) {\n\t\t\tconst local = transformBezierPath(region, invertMatrix(this.worldOf(node.id)));\n\t\t\tconst live = node.subpaths.filter((sp) => sp.anchors.length > 1);\n\t\t\tif (!live.length) continue;\n\t\t\tif (hasPaint(node.fill) && live.every((sp) => sp.closed)) {\n\t\t\t\tconst faces = eraseFromFill(subpathsToBezier(live), node.fillRule ?? 'nonzero', local);\n\t\t\t\tif (faces) plan.push({ node, pieces: faces.map((f) => bezierToSubpaths(f)) });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst results = live.map((sp) => eraseFromOutline(sp, local));\n\t\t\tif (results.every((r) => !r)) continue;\n\t\t\tconst subpaths = live.flatMap((sp, i) => results[i] ?? [sp]);\n\t\t\tplan.push({ node, pieces: live.length === 1 ? subpaths.map((sp) => [sp]) : [subpaths] });\n\t\t}\n\t\tif (!plan.length) return [];\n\t\tconst changed: string[] = [];\n\t\tthis.change('Erase', () => {\n\t\t\tfor (const { node, pieces } of plan) changed.push(...this.replaceWithPieces(node, pieces));\n\t\t\tthis.anchors = [];\n\t\t\tthis.dropStaleRefs();\n\t\t\tif (this.selection.length || options.ids) this.selection = [...new Set([...this.selection, ...changed.filter((id) => this.getNode(id))])];\n\t\t});\n\t\treturn changed;\n\t}\n\n\t/** Ends the pen path being drawn (a lone anchor is removed). */\n\tfinishPath(): void {\n\t\tif (!this.pen) return;\n\t\tconst node = this.getNode(this.pen.nodeId) as VectorPathNode | undefined;\n\t\tconst sp = node?.subpaths[this.pen.subpath];\n\t\tconst pen = this.pen;\n\t\tthis.pen = null;\n\t\tthis.pointer = null;\n\t\tif (node && sp && sp.anchors.length < 2) {\n\t\t\tconst subpaths = node.subpaths.filter((_, i) => i !== pen.subpath);\n\t\t\tthis.change('Delete Path', () => {\n\t\t\t\tthis.replaceNode(node.id, subpaths.length ? { ...node, subpaths } : null);\n\t\t\t\tif (!subpaths.length) {\n\t\t\t\t\tthis.selection = this.selection.filter((id) => id !== node.id);\n\t\t\t\t\tif (this.editingId === node.id) this.editingId = null;\n\t\t\t\t}\n\t\t\t\tthis.anchors = [];\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tthis.notify();\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Clipboard\n\t// -------------------------------------------------------------------------\n\n\t/** Copies the selection. Returns JSON text for the system clipboard (or null). */\n\tcopy(): string | null {\n\t\tconst nodes = this.selectedRoots()\n\t\t\t.map((id) => this.getNode(id))\n\t\t\t.filter((n): n is VectorNode => !!n)\n\t\t\t.map((n) => cloneVectorNode(n));\n\t\tif (!nodes.length) return null;\n\t\tthis.clipboard = nodes;\n\t\treturn JSON.stringify({ type: VECTOR_CLIPBOARD_TYPE, nodes });\n\t}\n\n\tcut(): string | null {\n\t\tconst text = this.copy();\n\t\tif (text) this.removeNodes(this.selectedRoots(), { label: 'Cut' });\n\t\treturn text;\n\t}\n\n\t/**\n\t * Pastes `text` (from `copy()`, or SVG markup) or the internal clipboard, offset by 10, 10.\n\t * Returns the new ids.\n\t */\n\tpaste(text?: string, options: { offset?: Vec2 } = {}): string[] {\n\t\tlet nodes: VectorNode[] | null = null;\n\t\tif (text) {\n\t\t\tconst trimmed = text.trim();\n\t\t\tif (trimmed.startsWith('{')) {\n\t\t\t\ttry {\n\t\t\t\t\tconst data = JSON.parse(trimmed);\n\t\t\t\t\tif (data?.type === VECTOR_CLIPBOARD_TYPE && Array.isArray(data.nodes)) nodes = data.nodes;\n\t\t\t\t} catch {\n\t\t\t\t\tnodes = null;\n\t\t\t\t}\n\t\t\t} else if (/<svg[\\s>]/i.test(trimmed)) {\n\t\t\t\ttry {\n\t\t\t\t\tnodes = parseVectorSvg(trimmed).nodes;\n\t\t\t\t} catch {\n\t\t\t\t\tnodes = null;\n\t\t\t\t}\n\t\t\t}\n\t\t} else nodes = this.clipboard;\n\t\tif (!nodes?.length) return [];\n\t\tconst offset = options.offset ?? (text && !text.trim().startsWith('{') ? { x: 0, y: 0 } : { x: 10, y: 10 });\n\t\tconst copies = nodes.map((n) => transformVectorNode(this.freshIds(n), translateMatrix(offset.x, offset.y)));\n\t\tthis.change('Paste', () => {\n\t\t\tthis.setDoc([...this.doc.nodes, ...copies]);\n\t\t\tthis.selection = copies.map((n) => n.id);\n\t\t\tthis.anchors = [];\n\t\t});\n\t\tif (!text) this.clipboard = copies.map((n) => cloneVectorNode(n));\n\t\treturn copies.map((n) => n.id);\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Undo\n\t// -------------------------------------------------------------------------\n\n\tundo(): boolean {\n\t\tthis.cancelDrag();\n\t\tthis.pen = null;\n\t\treturn this.history.undo();\n\t}\n\n\tredo(): boolean {\n\t\tthis.cancelDrag();\n\t\treturn this.history.redo();\n\t}\n\n\t/** Deletes selected anchors (direct tool) or nodes. */\n\tdeleteSelection(): VectorActionResult {\n\t\tif (this.tool === 'direct' && this.anchors.length) return this.deleteAnchors();\n\t\treturn this.removeNodes(this.selectedRoots());\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Pointer input\n\t// -------------------------------------------------------------------------\n\n\t/** The fields the anchor / handle helpers read, without building a full state. */\n\tprivate view() {\n\t\treturn { document: this.doc, selection: this.selection, editingId: this.editingId, tool: this.tool, pen: this.pen, selectedAnchors: this.anchors };\n\t}\n\n\tprivate tolerance() {\n\t\treturn this.hitRadius / this.viewport.zoom;\n\t}\n\n\tprivate hitTransformHandle(screen: Vec2): VectorTransformHandle | null {\n\t\tif (this.tool !== 'select' || !this.selection.length) return null;\n\t\tconst frame = this.frameOf(this.selectedRoots());\n\t\tif (!frame) return null;\n\t\tconst { box, handles } = getVectorSelectionBoxGeometry(frame, this.viewport);\n\t\tconst r = this.hitRadius + 2;\n\t\t// Tiny boxes: only corners and rotation, so the box can still be grabbed.\n\t\tconst usable = box.width < 2 * r || box.height < 2 * r ? handles.filter((h) => h.handle.length === 2 || h.handle === 'rotate') : handles;\n\t\tfor (const h of usable) if (Math.hypot(h.screen.x - screen.x, h.screen.y - screen.y) <= r * Math.SQRT2) return h.handle;\n\t\treturn null;\n\t}\n\n\tprivate hitHandle(screen: Vec2): VectorHandleRef | null {\n\t\tfor (const ref of getVisibleHandles(this.view()).reverse()) {\n\t\t\tconst a = getAnchorWorld(this.doc, ref);\n\t\t\tconst h = a?.[ref.handle];\n\t\t\tif (!h) continue;\n\t\t\tconst s = this.documentToScreen(h);\n\t\t\tif (Math.hypot(s.x - screen.x, s.y - screen.y) <= this.hitRadius) return ref;\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate cornerView() {\n\t\treturn { ...this.view(), viewport: this.viewport };\n\t}\n\n\tprivate hitCornerWidget(screen: Vec2): VectorCornerWidget | null {\n\t\tlet best: VectorCornerWidget | null = null;\n\t\tlet bestD = Infinity;\n\t\tfor (const w of getVectorCornerWidgets(this.cornerView())) {\n\t\t\tconst s = this.documentToScreen(w);\n\t\t\tconst d = Math.hypot(s.x - screen.x, s.y - screen.y);\n\t\t\tif (d <= this.hitRadius && d < bestD) {\n\t\t\t\tbest = w;\n\t\t\t\tbestD = d;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\tprivate hitAnchor(screen: Vec2, refs: readonly VectorAnchorRef[]): VectorAnchorRef | null {\n\t\tlet best: VectorAnchorRef | null = null;\n\t\tlet bestD = Infinity;\n\t\tfor (const ref of refs) {\n\t\t\tconst a = getAnchorWorld(this.doc, ref);\n\t\t\tif (!a) continue;\n\t\t\tconst s = this.documentToScreen(a);\n\t\t\tconst d = Math.hypot(s.x - screen.x, s.y - screen.y);\n\t\t\tif (d <= this.hitRadius && d < bestD) {\n\t\t\t\tbest = ref;\n\t\t\t\tbestD = d;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\t/** Deepest unlocked visible path under `p`. */\n\t/** Live groups being edited (selected, or holding the selection or the edited path): their children are hit as stored. */\n\tprivate enteredGroups(): Set<string> {\n\t\treturn vectorEnteredGroups(this.doc.nodes, this.editingId ? [...this.selection, this.editingId] : this.selection);\n\t}\n\n\tprivate hitPath(p: Vec2): VectorPathNode | null {\n\t\tconst tol = this.tolerance();\n\t\tconst entered = this.enteredGroups();\n\t\tconst search = (list: readonly VectorNode[], parent: AffineMatrix): VectorPathNode | null => {\n\t\t\tfor (let i = list.length - 1; i >= 0; i--) {\n\t\t\t\tconst n = list[i];\n\t\t\t\tif (n.visible === false || n.locked) continue;\n\t\t\t\tif (n.type === 'group') {\n\t\t\t\t\t// A live group not being edited is hit where it draws (a click in a subtracted\n\t\t\t\t\t// hole goes through), then picks the child under the pointer.\n\t\t\t\t\tif (hasOwnVectorAppearance(n) && !entered.has(n.id) && !hitTestVectorNode(n, p, tol, parent, { entered })) continue;\n\t\t\t\t\tconst found = search(n.children, multiplyMatrices(nodeMatrix(n), parent));\n\t\t\t\t\tif (found) return found;\n\t\t\t\t} else if (hitTestVectorNode(n, p, tol, parent, { entered })) return n;\n\t\t\t}\n\t\t\treturn null;\n\t\t};\n\t\treturn search(this.doc.nodes, IDENTITY_MATRIX);\n\t}\n\n\t/** Endpoints of open subpaths of unlocked visible paths (pen tool resumes from them). */\n\tprivate openEndpoints(): VectorAnchorRef[] {\n\t\tconst out: VectorAnchorRef[] = [];\n\t\twalkVectorNodes(this.doc.nodes, (n) => {\n\t\t\tif (n.type !== 'path' || n.locked || n.visible === false) return;\n\t\t\tn.subpaths.forEach((sp, s) => {\n\t\t\t\tif (sp.closed || sp.anchors.length < 1) return;\n\t\t\t\tout.push({ nodeId: n.id, subpath: s, index: 0 });\n\t\t\t\tif (sp.anchors.length > 1) out.push({ nodeId: n.id, subpath: s, index: sp.anchors.length - 1 });\n\t\t\t});\n\t\t});\n\t\treturn out;\n\t}\n\n\tprivate computeHover(screen: Vec2): VectorHover | null {\n\t\tconst p = this.screenToDocument(screen);\n\t\tswitch (this.tool) {\n\t\t\tcase 'select': {\n\t\t\t\tconst handle = this.hitTransformHandle(screen);\n\t\t\t\tif (handle) return { nodeId: null, transformHandle: handle };\n\t\t\t\tconst node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance(), { entered: this.enteredGroups() });\n\t\t\t\treturn node ? { nodeId: node.id } : null;\n\t\t\t}\n\t\t\tcase 'direct': {\n\t\t\t\tconst handle = this.hitHandle(screen);\n\t\t\t\tif (handle) return { nodeId: handle.nodeId, handle };\n\t\t\t\tconst anchor = this.hitAnchor(screen, getVisibleAnchors(this.view()));\n\t\t\t\tif (anchor) return { nodeId: anchor.nodeId, anchor };\n\t\t\t\tconst widget = this.hitCornerWidget(screen);\n\t\t\t\tif (widget) return { nodeId: widget.ref.nodeId, corner: widget.ref };\n\t\t\t\tfor (const id of getEditablePathIds(this.view())) {\n\t\t\t\t\tconst node = this.getNode(id) as VectorPathNode;\n\t\t\t\t\tconst seg = nearestSegment(node, this.toLocal(id, p));\n\t\t\t\t\tif (seg && seg.distance <= this.tolerance()) {\n\t\t\t\t\t\tconst point = applyMatrix(this.worldOf(id), seg.point);\n\t\t\t\t\t\treturn { nodeId: id, segment: { nodeId: id, subpath: seg.subpath, segment: seg.segment, t: seg.t, point } };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst path = this.hitPath(p);\n\t\t\t\treturn path ? { nodeId: path.id } : null;\n\t\t\t}\n\t\t\tcase 'pen': {\n\t\t\t\tconst refs = this.pen ? [{ nodeId: this.pen.nodeId, subpath: this.pen.subpath, index: 0 }] : this.openEndpoints();\n\t\t\t\tconst anchor = this.hitAnchor(screen, refs);\n\t\t\t\treturn anchor ? { nodeId: anchor.nodeId, anchor } : null;\n\t\t\t}\n\t\t\tcase 'scissors': {\n\t\t\t\tconst seg = this.nearestOutline(p);\n\t\t\t\treturn seg ? { nodeId: seg.nodeId, segment: seg } : null;\n\t\t\t}\n\t\t\tcase 'blend': {\n\t\t\t\tconst hit = this.blendHit(screen, p);\n\t\t\t\tif (!hit) return null;\n\t\t\t\treturn hit.anchor === null ? { nodeId: hit.nodeId } : { nodeId: hit.nodeId, anchor: { nodeId: hit.nodeId, subpath: 0, index: hit.anchor } };\n\t\t\t}\n\t\t\tcase 'width': {\n\t\t\t\tconst hit = this.hitWidthHandle(screen);\n\t\t\t\tif (hit) return { nodeId: hit.ref.nodeId, widthPoint: hit.ref };\n\t\t\t\tconst spot = this.strokeSpotAt(p);\n\t\t\t\treturn spot ? { nodeId: spot.nodeId, strokeSpot: spot } : null;\n\t\t\t}\n\t\t\tcase 'gradient': {\n\t\t\t\tconst a = this.getGradientAnnotator();\n\t\t\t\tconst part = a ? hitVectorGradientAnnotator(a, screen) : null;\n\t\t\t\tif (a && part) return { nodeId: a.nodeId, gradient: part };\n\t\t\t\tconst path = this.hitPath(p);\n\t\t\t\treturn path ? { nodeId: path.id } : null;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate widthView(): Pick<VectorEditorState, 'document' | 'selection' | 'tool' | 'hover' | 'interaction' | 'selectedWidthPoints'> {\n\t\treturn { document: this.doc, selection: this.selection, tool: this.tool, hover: this.hover, interaction: this.interaction, selectedWidthPoints: this.widthPoints };\n\t}\n\n\t/** The width point centre or side end under `screen` (width tool). */\n\tprivate hitWidthHandle(screen: Vec2): { ref: VectorWidthPointRef & { part: 'center' | 'left' | 'right' } } | null {\n\t\tconst shown = this.hitWidthHandleIn(screen, this.widthView());\n\t\tif (shown) return shown;\n\t\t// The points of the stroke under the pointer, when no hover has shown them yet.\n\t\tconst spot = this.strokeSpotAt(this.screenToDocument(screen));\n\t\treturn spot && spot.nodeId !== this.hover?.nodeId ? this.hitWidthHandleIn(screen, { ...this.widthView(), hover: { nodeId: spot.nodeId } }) : null;\n\t}\n\n\tprivate hitWidthHandleIn(screen: Vec2, view: Pick<VectorEditorState, 'document' | 'selection' | 'tool' | 'hover' | 'interaction' | 'selectedWidthPoints'>): { ref: VectorWidthPointRef & { part: 'center' | 'left' | 'right' } } | null {\n\t\tlet best: { ref: VectorWidthPointRef & { part: 'center' | 'left' | 'right' } } | null = null;\n\t\tlet bestD = Infinity;\n\t\tfor (const h of getVectorWidthHandles(view)) {\n\t\t\tfor (const [part, q] of [['center', h], ['left', h.left], ['right', h.right]] as const) {\n\t\t\t\tconst s = this.documentToScreen(q);\n\t\t\t\tconst d = Math.hypot(s.x - screen.x, s.y - screen.y);\n\t\t\t\t// Ties go to the centre (listed first), so a zero-width point can still be moved.\n\t\t\t\tif (d <= this.hitRadius && d < bestD - 1e-9) {\n\t\t\t\t\tbest = { ref: { ...h.ref, part } };\n\t\t\t\t\tbestD = d;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n\n\t/** The nearest stroked, unlocked visible path whose stroke is under document point `p` (width tool). */\n\tprivate strokeSpotAt(p: Vec2): VectorStrokeSpot | null {\n\t\tlet best: (VectorStrokeSpot & { distance: number }) | null = null;\n\t\tconst tol = this.tolerance();\n\t\twalkVectorNodes(this.doc.nodes, (n) => {\n\t\t\tif (n.type !== 'path' || n.visible === false || n.locked) return;\n\t\t\tif (hiddenOrLocked(this.doc.nodes, n.id)) return;\n\t\t\tconst g = vectorStrokeWidthGeometry(n);\n\t\t\tif (!g) return;\n\t\t\tconst m = this.worldOf(n.id);\n\t\t\tconst local = applyMatrix(invertMatrix(m), p);\n\t\t\tconst near = nearestStrokePosition(g.line, [local.x, local.y]);\n\t\t\tif (!near) return;\n\t\t\tconst profile = strokeWidthProfileOf(n);\n\t\t\tconst w = widthProfileAt(profile, near.t);\n\t\t\tconst reach = Math.max(near.side === 'left' ? w.left : w.right, 0) * g.unit + tol;\n\t\t\tif (near.distance > reach || near.distance > widthProfileMaxScale(profile) * g.unit + tol) return;\n\t\t\tif (best && near.distance >= best.distance) return;\n\t\t\tconst nx = near.tangent[1];\n\t\t\tconst ny = -near.tangent[0];\n\t\t\tconst [cx, cy] = near.point;\n\t\t\tbest = {\n\t\t\t\tnodeId: n.id,\n\t\t\t\tsubpath: near.contour,\n\t\t\t\tt: near.t,\n\t\t\t\tpoint: applyMatrix(m, { x: cx, y: cy }),\n\t\t\t\tleft: applyMatrix(m, { x: cx + nx * w.left * g.unit, y: cy + ny * w.left * g.unit }),\n\t\t\t\tright: applyMatrix(m, { x: cx - nx * w.right * g.unit, y: cy - ny * w.right * g.unit }),\n\t\t\t\tdistance: near.distance\n\t\t\t};\n\t\t});\n\t\tif (!best) return null;\n\t\tconst { distance: _d, ...spot } = best as VectorStrokeSpot & { distance: number };\n\t\treturn spot;\n\t}\n\n\tprivate downWidth(screen: Vec2, p: Vec2, input: VectorPointerInput) {\n\t\tconst hit = this.hitWidthHandle(screen);\n\t\tconst spot = hit ? null : this.strokeSpotAt(p);\n\t\tconst nodeId = hit?.ref.nodeId ?? spot?.nodeId;\n\t\tconst node = nodeId ? this.getNode(nodeId) : undefined;\n\t\tconst g = node?.type === 'path' ? vectorStrokeWidthGeometry(node) : null;\n\t\tif (this.widthEdit) this.widthEdit = null;\n\t\tif (!node || node.type !== 'path' || !g) {\n\t\t\t// A click away from strokes deselects (Shift keeps the selection).\n\t\t\tif (!input.shift) this.widthPoints = [];\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst before = this.snapshot();\n\t\tconst local = this.toLocal(node.id, p);\n\t\tlet profile: VectorWidthPoint[];\n\t\tlet ref: VectorWidthPointRef;\n\t\tlet mode: 'width' | 'move' | 'copy' = 'width';\n\t\tlet side: 'both' | 'left' | 'right' = 'both';\n\t\tlet group: number[] = [];\n\t\tlet shift = false;\n\t\tlet t: number;\n\t\tlet subpath: number;\n\t\tif (hit) {\n\t\t\tprofile = sanitizeWidthProfile(node.strokeWidthProfile);\n\t\t\tref = { nodeId: node.id, index: hit.ref.index, subpath: hit.ref.subpath };\n\t\t\tt = profile[ref.index]?.t ?? 0;\n\t\t\tsubpath = hit.ref.subpath;\n\t\t\tif (hit.ref.part === 'center') mode = input.alt && profile.length < VECTOR_WIDTH_PROFILE_MAX_POINTS ? 'copy' : 'move';\n\t\t\telse if (input.alt) side = hit.ref.part;\n\t\t\tconst same = this.widthPoints.length > 0 && this.widthPoints[0].nodeId === node.id;\n\t\t\tconst selected = same && this.widthPoints.some((r) => r.index === ref.index);\n\t\t\tif (input.shift && mode !== 'copy') {\n\t\t\t\t// Shift-click adds the point to the selection, or takes it out (and drags nothing).\n\t\t\t\tif (selected) {\n\t\t\t\t\tthis.widthPoints = this.widthPoints.filter((r) => r.index !== ref.index);\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.widthPoints = same ? [...this.widthPoints, ref] : [ref];\n\t\t\t\tshift = true;\n\t\t\t} else if (mode === 'copy' || !selected) this.widthPoints = [ref];\n\t\t\tif (mode !== 'copy') group = this.widthPoints.filter((r) => r.index !== ref.index).map((r) => r.index);\n\t\t} else {\n\t\t\tconst near = nearestStrokePosition(g.line, [local.x, local.y])!;\n\t\t\tconst inserted = insertWidthPoint(strokeWidthProfileOf(node), near.t);\n\t\t\tprofile = inserted.points;\n\t\t\tsubpath = near.contour;\n\t\t\tt = near.t;\n\t\t\tref = { nodeId: node.id, index: inserted.index, subpath };\n\t\t\tif (input.alt) side = near.side;\n\t\t\tthis.widthPoints = [ref];\n\t\t}\n\t\tconst at = strokePositionPoint(g.line, subpath, t);\n\t\tif (!at) return;\n\t\tconst base = { ...this.doc, nodes: mapTree(this.doc.nodes, (n) => (n.id === node.id ? { ...(n as VectorPathNode), strokeWidthProfile: profile } : n)) };\n\t\tthis.doc = base;\n\t\tthis.drag = {\n\t\t\tkind: 'width',\n\t\t\tref,\n\t\t\tmode,\n\t\t\tside,\n\t\t\tsource: ref.index,\n\t\t\tgroup,\n\t\t\tshift,\n\t\t\trenumber: null,\n\t\t\torigin: { x: at.point[0], y: at.point[1] },\n\t\t\tnormal: { x: at.tangent[1], y: -at.tangent[0] },\n\t\t\tunit: g.unit,\n\t\t\tprofile,\n\t\t\tbefore,\n\t\t\tbase,\n\t\t\tstart: p,\n\t\t\tmoved: false,\n\t\t\tcreated: !hit\n\t\t};\n\t\tconst w = profile[ref.index];\n\t\tthis.interaction = { type: 'width', ref, mode, width: (w.left + w.right) * g.unit };\n\t\tthis.notify(!hit);\n\t}\n\n\t/**\n\t * A width-tool slide to `t` on the dragged point's subpath lands exactly on another width\n\t * point (not `skip`) within the pick distance on screen: dropping a point onto another makes\n\t * a discontinuous point (two points at one `t`, the width steps there).\n\t */\n\tprivate snapWidthT(drag: Extract<Drag, { kind: 'width' }>, node: VectorPathNode, line: BezierPath, t: number, skip: number): number {\n\t\tconst m = this.worldOf(node.id, drag.base);\n\t\tconst at = strokePositionPoint(line, drag.ref.subpath, t);\n\t\tif (!at) return t;\n\t\tconst here = this.documentToScreen(applyMatrix(m, { x: at.point[0], y: at.point[1] }));\n\t\tlet best = t;\n\t\tlet bestD = this.hitRadius;\n\t\tdrag.profile.forEach((w, i) => {\n\t\t\tif (i === skip || drag.group.includes(i)) return;\n\t\t\tconst q = strokePositionPoint(line, drag.ref.subpath, w.t);\n\t\t\tif (!q) return;\n\t\t\tconst s = this.documentToScreen(applyMatrix(m, { x: q.point[0], y: q.point[1] }));\n\t\t\tconst d = Math.hypot(s.x - here.x, s.y - here.y);\n\t\t\tif (d <= bestD) {\n\t\t\t\tbest = w.t;\n\t\t\t\tbestD = d;\n\t\t\t}\n\t\t});\n\t\treturn best;\n\t}\n\n\t/** The nearest outline point of any visible, unlocked path within the pick distance of document point `p`. */\n\tprivate nearestOutline(p: Vec2): NonNullable<VectorHover['segment']> | null {\n\t\tlet best: (NonNullable<VectorHover['segment']> & { distance: number }) | null = null;\n\t\tconst tol = this.tolerance();\n\t\tconst visit = (list: readonly VectorNode[], parent: AffineMatrix) => {\n\t\t\tfor (let i = list.length - 1; i >= 0; i--) {\n\t\t\t\tconst n = list[i];\n\t\t\t\tif (n.visible === false || n.locked) continue;\n\t\t\t\tconst m = multiplyMatrices(nodeMatrix(n), parent);\n\t\t\t\tif (n.type === 'group') {\n\t\t\t\t\tvisit(n.children, m);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst hit = nearestSegment(n, applyMatrix(invertMatrix(m), p));\n\t\t\t\t// Node matrices are rigid, so local distances are document distances.\n\t\t\t\tif (hit && hit.distance <= tol && (!best || hit.distance < best.distance - 1e-9)) {\n\t\t\t\t\tbest = { nodeId: n.id, subpath: hit.subpath, segment: hit.segment, t: hit.t, point: applyMatrix(m, hit.point), distance: hit.distance };\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tvisit(this.doc.nodes, IDENTITY_MATRIX);\n\t\tif (!best) return null;\n\t\tconst { distance: _d, ...seg } = best as NonNullable<VectorHover['segment']> & { distance: number };\n\t\treturn seg;\n\t}\n\n\tprivate sameHover(a: VectorHover | null, b: VectorHover | null): boolean {\n\t\treturn JSON.stringify(a) === JSON.stringify(b);\n\t}\n\n\t/** Primary button down on the canvas at `screen` (pixels relative to the canvas). */\n\tpointerDown(screen: Vec2, input: VectorPointerInput = {}): void {\n\t\tthis.cancelDrag();\n\t\tif (input.button === 1 || this.spaceHeld) {\n\t\t\tthis.drag = { kind: 'pan', start: screen, viewport: this.viewport };\n\t\t\tthis.interaction = { type: 'pan' };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (input.button !== undefined && input.button !== 0) return;\n\t\tconst p = this.screenToDocument(screen);\n\t\tthis.pointer = p;\n\t\tswitch (this.tool) {\n\t\t\tcase 'select':\n\t\t\t\treturn this.downSelect(screen, p, input);\n\t\t\tcase 'direct':\n\t\t\t\treturn this.downDirect(screen, p, input);\n\t\t\tcase 'pen':\n\t\t\t\treturn this.downPen(screen, p, input);\n\t\t\tcase 'pencil':\n\t\t\t\treturn this.downPencil(p, input);\n\t\t\tcase 'width':\n\t\t\t\treturn this.downWidth(screen, p, input);\n\t\t\tcase 'shapeBuilder':\n\t\t\tcase 'paintBucket':\n\t\t\t\treturn this.downBuilder(p, input);\n\t\t\tcase 'blend':\n\t\t\t\treturn this.downBlend(screen, p);\n\t\t\tcase 'gradient':\n\t\t\t\treturn this.downGradient(screen, p, input);\n\t\t\tcase 'scissors': {\n\t\t\t\tconst seg = this.nearestOutline(p);\n\t\t\t\tif (seg) this.cutPathAt(seg.nodeId, seg.point);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'knife':\n\t\t\tcase 'eraser':\n\t\t\t\tthis.drag = { kind: 'stroke', tool: this.tool, points: [p], before: this.snapshot() };\n\t\t\t\tthis.interaction = { type: 'stroke', tool: this.tool, points: [p], width: this.tool === 'eraser' ? this.editing.eraserSize : 0 };\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\tdefault:\n\t\t\t\treturn this.downShape(p, input);\n\t\t}\n\t}\n\n\t/** The shape under the pointer for the Blend tool, and the anchor of its first subpath under it. */\n\tprivate blendHit(screen: Vec2, p: Vec2): VectorBlendStart | null {\n\t\tconst node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance());\n\t\tif (!node) return null;\n\t\tlet anchor: number | null = null;\n\t\tif (node.type === 'path' && node.subpaths[0]) {\n\t\t\tconst refs = node.subpaths[0].anchors.map((_, index) => ({ nodeId: node.id, subpath: 0, index }));\n\t\t\tanchor = this.hitAnchor(screen, refs)?.index ?? null;\n\t\t}\n\t\treturn { nodeId: node.id, anchor };\n\t}\n\n\t/** Blend tool: the first click picks the shape to blend from, the next one blends it into the shape clicked. */\n\tprivate downBlend(screen: Vec2, p: Vec2) {\n\t\tconst hit = this.blendHit(screen, p);\n\t\tconst start = this.blendStart;\n\t\tif (!hit) {\n\t\t\tif (start) {\n\t\t\t\tthis.blendStart = null;\n\t\t\t\tthis.notify();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (!start || start.nodeId === hit.nodeId) {\n\t\t\tthis.blendStart = hit;\n\t\t\tthis.selection = [hit.nodeId];\n\t\t\tthis.anchors = [];\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tthis.blendStart = null;\n\t\tconst first = this.getNode(start.nodeId);\n\t\tif (isVectorBlendGroup(first)) {\n\t\t\t// Clicking on from a blend adds the shape as its next key.\n\t\t\tif (!this.addBlendKey(first.id, hit.nodeId, { start: hit.anchor }).ok) this.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst starts: Record<string, number> = {};\n\t\tif (start.anchor !== null) starts[start.nodeId] = start.anchor;\n\t\tif (hit.anchor !== null) starts[hit.nodeId] = hit.anchor;\n\t\tif (!this.blend({ ids: [start.nodeId, hit.nodeId], keepOrder: true, starts })) this.notify();\n\t}\n\n\tprivate downSelect(screen: Vec2, p: Vec2, input: VectorPointerInput) {\n\t\tconst handle = this.hitTransformHandle(screen);\n\t\tif (handle) {\n\t\t\tconst frame = this.frameOf(this.selectedRoots())!;\n\t\t\tconst before = this.snapshot();\n\t\t\tconst center = applyMatrix(rotateMatrix(frame.angle), { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 });\n\t\t\tthis.drag =\n\t\t\t\thandle === 'rotate'\n\t\t\t\t\t? { kind: 'rotate', start: p, center, before, base: this.doc }\n\t\t\t\t\t: { kind: 'scale', handle, start: p, frame, before, base: this.doc };\n\t\t\tthis.interaction = handle === 'rotate' ? { type: 'rotate', angle: 0 } : { type: 'scale', handle, sx: 1, sy: 1 };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance(), { entered: this.enteredGroups() });\n\t\tif (node) {\n\t\t\tconst selected = this.selection.includes(node.id);\n\t\t\tif (input.shift) {\n\t\t\t\tthis.selection = selected ? this.selection.filter((id) => id !== node.id) : [...this.selection, node.id];\n\t\t\t\tif (selected) {\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else if (!selected) this.selection = [node.id];\n\t\t\tthis.anchors = [];\n\t\t\tconst before = this.snapshot();\n\t\t\tthis.drag = {\n\t\t\t\tkind: 'move',\n\t\t\t\tstart: p,\n\t\t\t\torigin: this.boundsOf(this.selectedRoots()),\n\t\t\t\tbefore,\n\t\t\t\tbase: this.doc,\n\t\t\t\tmoved: false,\n\t\t\t\tduplicated: false,\n\t\t\t\tclickId: selected && !input.shift ? node.id : null\n\t\t\t};\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (!input.shift) this.selection = [];\n\t\tthis.drag = { kind: 'marquee', start: p, base: this.selection, baseAnchors: [], anchorsMode: false };\n\t\tthis.interaction = { type: 'marquee', bounds: { x: p.x, y: p.y, width: 0, height: 0 } };\n\t\tthis.notify();\n\t}\n\n\tprivate downDirect(screen: Vec2, p: Vec2, input: VectorPointerInput) {\n\t\tconst handle = this.hitHandle(screen);\n\t\tif (handle) {\n\t\t\tthis.drag = { kind: 'handle', ref: handle, before: this.snapshot(), base: this.doc };\n\t\t\tthis.interaction = { type: 'handle', ref: handle };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst anchor = this.hitAnchor(screen, getVisibleAnchors(this.view()));\n\t\tif (anchor) {\n\t\t\tconst selected = this.anchors.some((a) => sameRef(a, anchor));\n\t\t\tthis.editingId = anchor.nodeId;\n\t\t\tif (input.alt) {\n\t\t\t\t// Alt-drag an anchor pulls out fresh symmetric handles.\n\t\t\t\tthis.anchors = [anchor];\n\t\t\t\tthis.drag = { kind: 'pull', ref: anchor, before: this.snapshot(), base: this.doc, label: 'Make Smooth', outOnly: false, closing: false, altBreaks: false };\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (input.shift) {\n\t\t\t\tthis.anchors = selected ? this.anchors.filter((a) => !sameRef(a, anchor)) : [...this.anchors, anchor];\n\t\t\t\tif (selected) {\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else if (!selected) this.anchors = [anchor];\n\t\t\tthis.drag = { kind: 'anchors', start: p, before: this.snapshot(), base: this.doc, moved: false, clickRef: selected && !input.shift ? anchor : null, lead: anchor };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst widget = this.hitCornerWidget(screen);\n\t\tif (widget) {\n\t\t\tconst refs = getVectorCornerWidgets(this.cornerView()).map((w) => w.ref);\n\t\t\tthis.drag = { kind: 'corner', widget, refs, start: p, before: this.snapshot(), base: this.doc, moved: false, alt: !!input.alt, shift: !!input.shift };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tfor (const id of getEditablePathIds(this.view())) {\n\t\t\tconst node = this.getNode(id) as VectorPathNode;\n\t\t\tconst seg = nearestSegment(node, this.toLocal(id, p));\n\t\t\tif (!seg || seg.distance > this.tolerance()) continue;\n\t\t\tconst sp = node.subpaths[seg.subpath];\n\t\t\tif (input.alt) {\n\t\t\t\tconst before = this.snapshot();\n\t\t\t\tconst { subpath, index } = insertAnchor(sp, seg.segment, seg.t);\n\t\t\t\tthis.replaceNode(id, { ...node, subpaths: node.subpaths.map((s, i) => (i === seg.subpath ? subpath : s)) });\n\t\t\t\tconst ref = { nodeId: id, subpath: seg.subpath, index };\n\t\t\t\tthis.anchors = [ref];\n\t\t\t\tthis.editingId = id;\n\t\t\t\tthis.drag = { kind: 'anchors', start: p, before, base: this.doc, moved: true, clickRef: null, lead: ref };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// A click selects the segment's anchors; a drag bends it through the pointer.\n\t\t\tconst a = { nodeId: id, subpath: seg.subpath, index: seg.segment };\n\t\t\tconst b = { nodeId: id, subpath: seg.subpath, index: (seg.segment + 1) % sp.anchors.length };\n\t\t\tthis.anchors = input.shift ? [...this.anchors.filter((r) => !sameRef(r, a) && !sameRef(r, b)), a, b] : [a, b];\n\t\t\tthis.editingId = id;\n\t\t\tthis.drag = { kind: 'bend', ref: { nodeId: id, subpath: seg.subpath, segment: seg.segment }, t: seg.t, start: p, before: this.snapshot(), base: this.doc, moved: false };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tconst path = this.hitPath(p);\n\t\tif (path) {\n\t\t\tthis.editingId = path.id;\n\t\t\tif (!input.shift) this.selection = [path.id];\n\t\t\telse if (!this.selection.includes(path.id)) this.selection = [...this.selection, path.id];\n\t\t\tthis.anchors = path.subpaths.flatMap((sp, s) => sp.anchors.map((_, i) => ({ nodeId: path.id, subpath: s, index: i })));\n\t\t\tthis.drag = { kind: 'anchors', start: p, before: this.snapshot(), base: this.doc, moved: false, clickRef: null, lead: null };\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (!input.shift) {\n\t\t\tthis.anchors = [];\n\t\t}\n\t\tthis.drag = { kind: 'marquee', start: p, base: this.selection, baseAnchors: this.anchors, anchorsMode: getEditablePathIds(this.view()).length > 0 };\n\t\tthis.interaction = { type: 'marquee', bounds: { x: p.x, y: p.y, width: 0, height: 0 } };\n\t\tthis.notify();\n\t}\n\n\tprivate downPen(screen: Vec2, p: Vec2, input: VectorPointerInput) {\n\t\tconst before = this.snapshot();\n\t\tconst sp = this.pen ? (this.getNode(this.pen.nodeId) as VectorPathNode | undefined)?.subpaths[this.pen.subpath] : undefined;\n\t\tif (this.pen && sp) {\n\t\t\tconst pen = this.pen;\n\t\t\tconst first = { nodeId: pen.nodeId, subpath: pen.subpath, index: 0 };\n\t\t\tif (sp.anchors.length >= 2 && this.hitAnchor(screen, [first])) {\n\t\t\t\tconst node = this.getNode(pen.nodeId) as VectorPathNode;\n\t\t\t\tthis.replaceNode(pen.nodeId, { ...node, subpaths: node.subpaths.map((s, i) => (i === pen.subpath ? { ...s, closed: true } : s)) });\n\t\t\t\tthis.drag = { kind: 'pull', ref: first, before, base: this.doc, label: 'Close Path', outOnly: false, closing: true, altBreaks: true };\n\t\t\t\tthis.interaction = { type: 'pen', nodeId: pen.nodeId };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst last = getAnchorWorld(this.doc, { nodeId: pen.nodeId, subpath: pen.subpath, index: sp.anchors.length - 1 })!;\n\t\t\tconst leave = { at: { x: last.x, y: last.y }, constrain: input.shift ? ('angle' as const) : null };\n\t\t\tlet pt = this.snapDrawn(p, leave, input, () => this.drawingTargets(pen.nodeId));\n\t\t\tpt = this.toLocal(pen.nodeId, pt);\n\t\t\tconst node = this.getNode(pen.nodeId) as VectorPathNode;\n\t\t\tconst anchors = [...sp.anchors, { x: pt.x, y: pt.y }];\n\t\t\tthis.replaceNode(pen.nodeId, { ...node, subpaths: node.subpaths.map((s, i) => (i === pen.subpath ? { ...s, anchors } : s)) });\n\t\t\tconst ref = { nodeId: pen.nodeId, subpath: pen.subpath, index: anchors.length - 1 };\n\t\t\tthis.anchors = [ref];\n\t\t\tthis.drag = { kind: 'pull', ref, before, base: this.doc, label: 'Add Point', outOnly: false, closing: false, altBreaks: true };\n\t\t\tthis.interaction = { type: 'pen', nodeId: pen.nodeId };\n\t\t\tthis.notify(true);\n\t\t\treturn;\n\t\t}\n\t\tconst endpoint = this.hitAnchor(screen, this.openEndpoints());\n\t\tif (endpoint) {\n\t\t\tlet node = this.getNode(endpoint.nodeId) as VectorPathNode;\n\t\t\tlet index = endpoint.index;\n\t\t\tif (index === 0 && node.subpaths[endpoint.subpath].anchors.length > 1) {\n\t\t\t\tnode = { ...node, subpaths: node.subpaths.map((s, i) => (i === endpoint.subpath ? reverseSubpath(s) : s)) };\n\t\t\t\tthis.replaceNode(node.id, node);\n\t\t\t\tindex = node.subpaths[endpoint.subpath].anchors.length - 1;\n\t\t\t}\n\t\t\tthis.pen = { nodeId: node.id, subpath: endpoint.subpath };\n\t\t\tthis.selection = [node.id];\n\t\t\tthis.editingId = node.id;\n\t\t\tconst ref = { nodeId: node.id, subpath: endpoint.subpath, index };\n\t\t\tthis.anchors = [ref];\n\t\t\tthis.drag = { kind: 'pull', ref, before, base: this.doc, label: 'Continue Path', outOnly: true, closing: false, altBreaks: true };\n\t\t\tthis.interaction = { type: 'pen', nodeId: node.id };\n\t\t\tthis.notify(this.doc !== before.doc);\n\t\t\treturn;\n\t\t}\n\t\tconst pt = this.snapDrawn(p, null, input, () => this.drawingTargets(null));\n\t\tconst node: VectorPathNode = { id: this.newId(), type: 'path', name: 'Path', ...this.styleFor(true), subpaths: [{ anchors: [{ x: pt.x, y: pt.y }], closed: false }] };\n\t\tthis.setDoc([...this.doc.nodes, node]);\n\t\tthis.pen = { nodeId: node.id, subpath: 0 };\n\t\tthis.selection = [node.id];\n\t\tthis.editingId = node.id;\n\t\tconst ref = { nodeId: node.id, subpath: 0, index: 0 };\n\t\tthis.anchors = [ref];\n\t\tthis.drag = { kind: 'pull', ref, before, base: this.doc, label: 'Start Path', outOnly: false, closing: false, altBreaks: true };\n\t\tthis.interaction = { type: 'pen', nodeId: node.id };\n\t\tthis.notify(true);\n\t}\n\n\tprivate downPencil(p: Vec2, input: VectorPointerInput = {}) {\n\t\tconst before = this.snapshot();\n\t\tconst node: VectorPathNode = {\n\t\t\tid: this.newId(),\n\t\t\ttype: 'path',\n\t\t\tname: 'Pencil',\n\t\t\t...this.styleFor(true),\n\t\t\tstrokeLinecap: this.defaults.strokeLinecap ?? 'round',\n\t\t\tstrokeLinejoin: this.defaults.strokeLinejoin ?? 'round',\n\t\t\tsubpaths: [{ anchors: [{ x: p.x, y: p.y }], closed: false }]\n\t\t};\n\t\tthis.setDoc([...this.doc.nodes, node]);\n\t\tconst lazy = new LazyMouse((this.editing.pencilSmoothing * VECTOR_PENCIL_STRING_PX) / this.viewport.zoom);\n\t\tlazy.push([p.x, p.y]);\n\t\tconst mode = this.editing.pencilPressure;\n\t\tconst hasPressure = typeof input.pressure === 'number' && Number.isFinite(input.pressure);\n\t\tconst record = hasPressure && (mode === 'all' || (mode === 'pen' && input.pointerType === 'pen'));\n\t\tconst pressure = hasPressure ? input.pressure! : 0.5;\n\t\tthis.drag = { kind: 'pencil', nodeId: node.id, points: [[p.x, p.y]], before, lazy, pressures: record ? [pressure] : null, pressure };\n\t\tthis.interaction = { type: 'draw', tool: 'pencil', nodeId: node.id, bounds: { x: p.x, y: p.y, width: 0, height: 0 } };\n\t\tthis.notify(true);\n\t}\n\n\tprivate downShape(p: Vec2, input: VectorPointerInput) {\n\t\tconst before = this.snapshot();\n\t\t// The targets are gathered before the new shape joins the document, and kept for the drag.\n\t\tconst targets = this.drawingTargets(null);\n\t\tconst start = this.snapDrawn(p, null, input, () => targets);\n\t\tconst kind = this.tool as VectorShapeInput['kind'];\n\t\tconst node = this.createShapeNode({ kind, x: start.x, y: start.y, width: 0, height: 0 });\n\t\tthis.setDoc([...this.doc.nodes, node]);\n\t\tthis.drag = { kind: 'shape', tool: this.tool, start, nodeId: node.id, before, targets };\n\t\tthis.interaction = { type: 'draw', tool: this.tool, nodeId: node.id, bounds: { x: start.x, y: start.y, width: 0, height: 0 } };\n\t\tthis.notify(true);\n\t}\n\n\t/** Pointer moved over the canvas (with or without a button down). */\n\tpointerMove(screen: Vec2, input: VectorPointerInput = {}): void {\n\t\tconst p = this.screenToDocument(screen);\n\t\tconst drag = this.drag;\n\t\tif (!drag && (this.tool === 'shapeBuilder' || this.tool === 'paintBucket')) {\n\t\t\tthis.pointer = p;\n\t\t\tif (this.hoverBuilder(p, !!input.alt)) this.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (!drag) {\n\t\t\tconst hover = this.computeHover(screen);\n\t\t\tconst pointerMatters = this.tool === 'pen' && !!this.pen;\n\t\t\tconst guides = this.guides;\n\t\t\tthis.hover = hover;\n\t\t\tconst pointer = this.hoverSnap(p, input);\n\t\t\tif (this.sameHover(hover, this.state.hover) && !pointerMatters && JSON.stringify(guides) === JSON.stringify(this.guides)) return;\n\t\t\tthis.pointer = pointer;\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tthis.pointer = p;\n\t\tswitch (drag.kind) {\n\t\t\tcase 'pan':\n\t\t\t\tthis.viewport = { ...drag.viewport, x: drag.viewport.x + screen.x - drag.start.x, y: drag.viewport.y + screen.y - drag.start.y };\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\tcase 'gradient':\n\t\t\t\treturn this.moveGradient(drag, screen, p, input);\n\t\t\tcase 'move': {\n\t\t\t\tlet dx = p.x - drag.start.x;\n\t\t\t\tlet dy = p.y - drag.start.y;\n\t\t\t\tif (!drag.moved && Math.hypot(dx, dy) * this.viewport.zoom < 2) return;\n\t\t\t\tdrag.moved = true;\n\t\t\t\tif (input.shift) Math.abs(dx) > Math.abs(dy) ? (dy = 0) : (dx = 0);\n\t\t\t\tif (input.alt && !drag.duplicated) {\n\t\t\t\t\tdrag.duplicated = true;\n\t\t\t\t\tconst created: string[] = [];\n\t\t\t\t\tdrag.base = this.withDuplicates(drag.base, this.selectedRoots(), { x: 0, y: 0 }, created);\n\t\t\t\t\tthis.selection = created;\n\t\t\t\t\t// The originals stay behind as targets.\n\t\t\t\t\tdrag.targets = undefined;\n\t\t\t\t}\n\t\t\t\t({ dx, dy } = this.snapMove(drag, dx, dy, input));\n\t\t\t\tthis.doc = this.transformed(drag.base, new Set(this.selectedRoots()), translateMatrix(dx, dy));\n\t\t\t\tthis.interaction = { type: 'move', dx, dy };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'scale': {\n\t\t\t\t// Scale in the box's own (possibly rotated) frame.\n\t\t\t\tconst angle = drag.frame.angle;\n\t\t\t\tconst q = angle ? applyMatrix(rotateMatrix(-angle), p) : p;\n\t\t\t\tlet { sx, sy, origin } = scaleFor(drag.handle, drag.frame, q, input);\n\t\t\t\tif (this.snap.objects && !input.mod && drag.handle !== 'rotate') {\n\t\t\t\t\tdrag.targets ??= VectorSnapTargets.forResize(drag.base, this.selectedRoots());\n\t\t\t\t\tconst res = drag.targets.snapScale(drag.frame, drag.handle, sx, sy, input, this.snapTolerance());\n\t\t\t\t\t({ sx, sy } = res);\n\t\t\t\t\tthis.setGuides(res);\n\t\t\t\t} else this.setGuides(null);\n\t\t\t\tconst ids = new Set(this.selectedRoots());\n\t\t\t\tthis.doc = this.transformed(drag.base, ids, this.inFrame(angle, scaleMatrix(sx, sy, origin)));\n\t\t\t\tthis.interaction = { type: 'scale', handle: drag.handle, sx, sy };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'rotate': {\n\t\t\t\tconst a0 = Math.atan2(drag.start.y - drag.center.y, drag.start.x - drag.center.x);\n\t\t\t\tconst a1 = Math.atan2(p.y - drag.center.y, p.x - drag.center.x);\n\t\t\t\tlet angle = ((a1 - a0) * 180) / Math.PI;\n\t\t\t\tif (input.shift) angle = Math.round(angle / 15) * 15;\n\t\t\t\tthis.doc = this.transformed(drag.base, new Set(this.selectedRoots()), rotateMatrix(angle, drag.center));\n\t\t\t\tthis.interaction = { type: 'rotate', angle };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'marquee': {\n\t\t\t\tconst bounds = boundsFromPoints(drag.start, p);\n\t\t\t\tthis.interaction = { type: 'marquee', bounds };\n\t\t\t\tif (drag.anchorsMode) {\n\t\t\t\t\tconst inside = getVisibleAnchors(this.view()).filter((r) => {\n\t\t\t\t\t\tconst a = getAnchorWorld(this.doc, r)!;\n\t\t\t\t\t\treturn a.x >= bounds.x && a.x <= bounds.x + bounds.width && a.y >= bounds.y && a.y <= bounds.y + bounds.height;\n\t\t\t\t\t});\n\t\t\t\t\tthis.anchors = [...drag.baseAnchors, ...inside.filter((r) => !drag.baseAnchors.some((b) => sameRef(b, r)))];\n\t\t\t\t} else {\n\t\t\t\t\tconst hits = this.doc.nodes\n\t\t\t\t\t\t.filter((n) => !n.locked && n.visible !== false)\n\t\t\t\t\t\t.filter((n) => {\n\t\t\t\t\t\t\tconst b = vectorNodeBounds(n);\n\t\t\t\t\t\t\treturn b && boundsIntersect(b, bounds);\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.map((n) => n.id);\n\t\t\t\t\tthis.selection = [...new Set([...drag.base, ...hits])];\n\t\t\t\t}\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'anchors': {\n\t\t\t\tlet dx = p.x - drag.start.x;\n\t\t\t\tlet dy = p.y - drag.start.y;\n\t\t\t\tif (!drag.moved && Math.hypot(dx, dy) * this.viewport.zoom < 2) return;\n\t\t\t\tdrag.moved = true;\n\t\t\t\tconst lead = this.leadAnchor(drag);\n\t\t\t\tif (lead) {\n\t\t\t\t\t// The grabbed (or nearest) anchor snaps; the others move with it.\n\t\t\t\t\tconst leave = input.shift ? { at: lead, constrain: 'axis' as const, curves: false } : null;\n\t\t\t\t\tconst q = this.snapDrawn({ x: lead.x + dx, y: lead.y + dy }, leave, input, () => (drag.targets ??= VectorSnapTargets.forAnchorDrag(drag.base, this.anchors)));\n\t\t\t\t\tdx = q.x - lead.x;\n\t\t\t\t\tdy = q.y - lead.y;\n\t\t\t\t} else if (input.shift) Math.abs(dx) > Math.abs(dy) ? (dy = 0) : (dx = 0);\n\t\t\t\tconst single = this.anchors.length === 1 ? this.anchors[0] : null;\n\t\t\t\tconst from = single ? getAnchorWorld(drag.base, single) : undefined;\n\t\t\t\t// A lone point bends its segments with the Curvature setting (Alt held flips it).\n\t\t\t\tif (single && from && this.editing.curvatureMoves !== !!input.alt) this.doc = this.curvatureMoved(drag.base, single, { x: from.x + dx, y: from.y + dy });\n\t\t\t\telse this.doc = this.movedAnchors(drag.base, this.anchors, dx, dy);\n\t\t\t\tthis.interaction = { type: 'anchors', dx, dy };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'handle': {\n\t\t\t\tconst anchor = getAnchorWorld(drag.base, drag.ref);\n\t\t\t\tconst leave = anchor ? { at: { x: anchor.x, y: anchor.y }, constrain: input.shift ? ('angle' as const) : null } : null;\n\t\t\t\tconst pt = this.snapDrawn(p, leave, input, () => (drag.targets ??= VectorSnapTargets.forHandle(drag.base, drag.ref.nodeId)));\n\t\t\t\tconst local = this.toLocal(drag.ref.nodeId, pt, drag.base);\n\t\t\t\tthis.doc = this.updateAnchors(drag.base, [drag.ref], (a) => moveAnchorHandle(a, drag.ref.handle, local, input.alt));\n\t\t\t\tthis.interaction = { type: 'handle', ref: drag.ref };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'corner': {\n\t\t\t\tif (!drag.moved && Math.hypot(p.x - drag.start.x, p.y - drag.start.y) * this.viewport.zoom < 3) return;\n\t\t\t\tdrag.moved = true;\n\t\t\t\tconst c = drag.widget.corner;\n\t\t\t\tconst inv = invertMatrix(this.worldOf(drag.widget.ref.nodeId, drag.base));\n\t\t\t\tconst d = applyMatrixLinear(inv, { x: p.x - drag.start.x, y: p.y - drag.start.y });\n\t\t\t\tconst radius = Math.max(0, Math.min(c.maxRadius, drag.widget.drawnRadius + cornerRadiusChange(c, d)));\n\t\t\t\tthis.doc = this.updateAnchors(drag.base, drag.refs, (a) => setAnchorCorner(a, radius));\n\t\t\t\tthis.interaction = { type: 'corner', ref: drag.widget.ref, radius };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'bend': {\n\t\t\t\tif (!drag.moved && Math.hypot(p.x - drag.start.x, p.y - drag.start.y) * this.viewport.zoom < 2) return;\n\t\t\t\tdrag.moved = true;\n\t\t\t\tthis.doc = this.bent(drag.base, drag.ref, drag.t, p);\n\t\t\t\tthis.interaction = { type: 'bend', ref: drag.ref };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'builder':\n\t\t\t\treturn this.moveBuilder(drag, p);\n\t\t\tcase 'stroke': {\n\t\t\t\tconst last = drag.points[drag.points.length - 1];\n\t\t\t\tif (Math.hypot(p.x - last.x, p.y - last.y) * this.viewport.zoom < 1.5) return;\n\t\t\t\tdrag.points.push(p);\n\t\t\t\tthis.interaction = { type: 'stroke', tool: drag.tool, points: drag.points.slice(), width: drag.tool === 'eraser' ? this.editing.eraserSize : 0 };\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'pull': {\n\t\t\t\tconst anchor = getAnchorWorld(drag.base, drag.ref);\n\t\t\t\tif (!anchor) return;\n\t\t\t\tif (Math.hypot(p.x - anchor.x, p.y - anchor.y) * this.viewport.zoom < 2) {\n\t\t\t\t\tthis.doc = drag.base;\n\t\t\t\t\tthis.notify(true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.guides = null;\n\t\t\t\tlet pt = p;\n\t\t\t\tif (input.shift) pt = constrain45(anchor, pt);\n\t\t\t\tconst side = drag.closing ? 'in' : 'out';\n\t\t\t\t// Dragging out of the closing anchor pulls its incoming handle toward the pointer's mirror.\n\t\t\t\tconst pos = this.toLocal(drag.ref.nodeId, drag.closing ? { x: 2 * anchor.x - pt.x, y: 2 * anchor.y - pt.y } : pt, drag.base);\n\t\t\t\tthis.doc = this.updateAnchors(drag.base, [drag.ref], (a) => {\n\t\t\t\t\tif (drag.outOnly || (drag.altBreaks && input.alt)) return moveAnchorHandle(a, side, pos, true);\n\t\t\t\t\treturn moveAnchorHandle({ ...a, type: 'symmetric' }, side, pos);\n\t\t\t\t});\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'shape': {\n\t\t\t\tconst node = this.getNode(drag.nodeId);\n\t\t\t\tif (node?.type !== 'path') return;\n\t\t\t\tconst leave = { at: drag.start, constrain: input.shift ? (drag.tool === 'line' ? ('angle' as const) : ('square' as const)) : null, curves: false };\n\t\t\t\tlet end = this.snapDrawn(p, leave, input, () => (drag.targets ??= this.drawingTargets(null)));\n\t\t\t\tif (!this.snappedToObject && drag.tool !== 'line' && !input.shift && !input.mod && this.snap.objects && drag.targets) end = this.matchShapeSize(drag.targets, drag.start, end, input.alt);\n\t\t\t\tlet start = drag.start;\n\t\t\t\tlet bounds: VectorBounds;\n\t\t\t\tif (drag.tool === 'line') {\n\t\t\t\t\tif (input.shift) end = constrain45(start, end);\n\t\t\t\t\tif (input.alt) start = { x: 2 * start.x - end.x, y: 2 * start.y - end.y };\n\t\t\t\t\tbounds = { x: start.x, y: start.y, width: end.x - start.x, height: end.y - start.y };\n\t\t\t\t} else {\n\t\t\t\t\tlet w = end.x - start.x;\n\t\t\t\t\tlet h = end.y - start.y;\n\t\t\t\t\tif (input.shift) {\n\t\t\t\t\t\tconst s = Math.max(Math.abs(w), Math.abs(h));\n\t\t\t\t\t\tw = Math.sign(w || 1) * s;\n\t\t\t\t\t\th = Math.sign(h || 1) * s;\n\t\t\t\t\t}\n\t\t\t\t\tif (input.alt) start = { x: start.x - w, y: start.y - h };\n\t\t\t\t\tconst x2 = drag.start.x + w;\n\t\t\t\t\tconst y2 = drag.start.y + h;\n\t\t\t\t\tbounds = boundsFromPoints(start, { x: x2, y: y2 });\n\t\t\t\t}\n\t\t\t\tconst shaped = this.createShapeNode({ kind: drag.tool as VectorShapeInput['kind'], ...bounds });\n\t\t\t\tthis.replaceNode(drag.nodeId, { ...node, subpaths: shaped.subpaths });\n\t\t\t\tthis.interaction = { type: 'draw', tool: drag.tool, nodeId: drag.nodeId, bounds };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'width': {\n\t\t\t\tif (!drag.moved && Math.hypot(p.x - drag.start.x, p.y - drag.start.y) * this.viewport.zoom < 2) return;\n\t\t\t\tdrag.moved = true;\n\t\t\t\tconst node = findVectorNode(drag.base.nodes, drag.ref.nodeId) as VectorPathNode;\n\t\t\t\tconst local = this.toLocal(node.id, p, drag.base);\n\t\t\t\tconst src = drag.profile[drag.source];\n\t\t\t\tlet profile: VectorWidthPoint[];\n\t\t\t\tlet index = drag.source;\n\t\t\t\tif (drag.mode !== 'width') {\n\t\t\t\t\tconst g = vectorStrokeWidthGeometry(node);\n\t\t\t\t\tconst near = g ? nearestStrokePosition(g.line, [local.x, local.y], drag.ref.subpath) : null;\n\t\t\t\t\tif (!g || !near) return;\n\t\t\t\t\tconst t = drag.group.length ? near.t : this.snapWidthT(drag, node, g.line, near.t, drag.source);\n\t\t\t\t\tif (drag.mode === 'copy') {\n\t\t\t\t\t\t// The source stays; a copy of it slides (and may pass other points).\n\t\t\t\t\t\t({ points: profile, index } = insertWidthPoint(drag.profile, t, { left: src.left, right: src.right }, { step: true }));\n\t\t\t\t\t} else if (drag.group.length) {\n\t\t\t\t\t\tconst shifted = shiftWidthPoints(drag.profile, [drag.source, ...drag.group], t - src.t);\n\t\t\t\t\t\tprofile = shifted.points;\n\t\t\t\t\t\tdrag.renumber = shifted.renumber;\n\t\t\t\t\t\tindex = shifted.renumber.get(drag.source) ?? drag.source;\n\t\t\t\t\t} else profile = setWidthPoint(drag.profile, drag.source, { t });\n\t\t\t\t} else {\n\t\t\t\t\tconst off = ((local.x - drag.origin.x) * drag.normal.x + (local.y - drag.origin.y) * drag.normal.y) / drag.unit;\n\t\t\t\t\tconst patch =\n\t\t\t\t\t\tdrag.side === 'both' ? { left: Math.abs(off), right: Math.abs(off) } : drag.side === 'left' ? { left: Math.max(0, off) } : { right: Math.max(0, -off) };\n\t\t\t\t\tprofile = setWidthPoint(drag.profile, drag.source, patch);\n\t\t\t\t\tif (drag.group.length) {\n\t\t\t\t\t\t// Every selected point widens by as much as the dragged one.\n\t\t\t\t\t\tconst dl = profile[drag.source].left - src.left;\n\t\t\t\t\t\tconst dr = profile[drag.source].right - src.right;\n\t\t\t\t\t\tfor (const i of drag.group) profile = setWidthPoint(profile, i, { left: Math.max(0, drag.profile[i].left + dl), right: Math.max(0, drag.profile[i].right + dr) });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.doc = { ...drag.base, nodes: mapTree(drag.base.nodes, (n) => (n.id === node.id ? { ...(n as VectorPathNode), strokeWidthProfile: profile } : n)) };\n\t\t\t\tdrag.ref = { ...drag.ref, index };\n\t\t\t\tconst w = profile[index];\n\t\t\t\tthis.interaction = { type: 'width', ref: drag.ref, mode: drag.mode, width: (w.left + w.right) * drag.unit };\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'pencil': {\n\t\t\t\tconst last = drag.points[drag.points.length - 1];\n\t\t\t\tlet moved = false;\n\t\t\t\tif (typeof input.pressure === 'number' && Number.isFinite(input.pressure)) drag.pressure = input.pressure;\n\t\t\t\tfor (const q of drag.lazy.push([p.x, p.y])) {\n\t\t\t\t\tif (Math.hypot(q[0] - last[0], q[1] - last[1]) * this.viewport.zoom < 1.5) continue;\n\t\t\t\t\tdrag.points.push(q);\n\t\t\t\t\tdrag.pressures?.push(drag.pressure);\n\t\t\t\t\tmoved = true;\n\t\t\t\t}\n\t\t\t\tconst string = drag.lazy.string();\n\t\t\t\tconst node = this.getNode(drag.nodeId);\n\t\t\t\tif (node?.type !== 'path') return;\n\t\t\t\tif (!moved) {\n\t\t\t\t\tif (string && this.interaction?.type === 'draw') {\n\t\t\t\t\t\tthis.interaction = { ...this.interaction, string: [{ x: string[0][0], y: string[0][1] }, { x: string[1][0], y: string[1][1] }] };\n\t\t\t\t\t\tthis.notify();\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.replaceNode(drag.nodeId, { ...node, subpaths: [{ anchors: drag.points.map(([x, y]) => ({ x, y })), closed: false }] });\n\t\t\t\tconst xs = drag.points.map((q) => q[0]);\n\t\t\t\tconst ys = drag.points.map((q) => q[1]);\n\t\t\t\tthis.interaction = {\n\t\t\t\t\ttype: 'draw',\n\t\t\t\t\ttool: 'pencil',\n\t\t\t\t\tnodeId: drag.nodeId,\n\t\t\t\t\tbounds: { x: Math.min(...xs), y: Math.min(...ys), width: Math.max(...xs) - Math.min(...xs), height: Math.max(...ys) - Math.min(...ys) },\n\t\t\t\t\t...(string ? { string: [{ x: string[0][0], y: string[0][1] }, { x: string[1][0], y: string[1][1] }] as [Vec2, Vec2] } : {})\n\t\t\t\t};\n\t\t\t\tthis.notify(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Snaps a move of the selection by (dx, dy): its bounds line up with targets or space evenly, else go to the grid. */\n\tprivate snapMove(drag: Extract<Drag, { kind: 'move' }>, dx: number, dy: number, input: VectorModifiers): { dx: number; dy: number } {\n\t\tconst origin = drag.origin;\n\t\tif (input.mod || !origin) {\n\t\t\tthis.setGuides(null);\n\t\t\treturn { dx, dy };\n\t\t}\n\t\t// A Shift-locked axis stays put.\n\t\tconst axes = { x: !input.shift || dx !== 0, y: !input.shift || dy !== 0 };\n\t\tlet res = null;\n\t\tif (this.snap.objects) {\n\t\t\tdrag.targets ??= VectorSnapTargets.forMove(drag.base, this.selectedRoots());\n\t\t\tres = drag.targets.snapRect({ ...origin, x: origin.x + dx, y: origin.y + dy }, this.snapTolerance(), { spacing: true, axes });\n\t\t}\n\t\tthis.setGuides(res);\n\t\treturn {\n\t\t\tdx: res?.x ? dx + res.dx : axes.x ? this.gridValue(origin.x + dx) - origin.x : dx,\n\t\t\tdy: res?.y ? dy + res.dy : axes.y ? this.gridValue(origin.y + dy) - origin.y : dy\n\t\t};\n\t}\n\n\t/** The anchor a point drag snaps by, where it started: the grabbed one, else the moving one nearest the press. */\n\tprivate leadAnchor(drag: Extract<Drag, { kind: 'anchors' }>): Vec2 | null {\n\t\tconst ref = drag.lead && this.anchors.some((a) => sameRef(a, drag.lead!)) ? drag.lead : null;\n\t\tif (ref) {\n\t\t\tconst a = getAnchorWorld(drag.base, ref);\n\t\t\treturn a ? { x: a.x, y: a.y } : null;\n\t\t}\n\t\tlet best: Vec2 | null = null;\n\t\tfor (const r of this.anchors) {\n\t\t\tconst a = getAnchorWorld(drag.base, r);\n\t\t\tif (a && (!best || Math.hypot(a.x - drag.start.x, a.y - drag.start.y) < Math.hypot(best.x - drag.start.x, best.y - drag.start.y))) best = { x: a.x, y: a.y };\n\t\t}\n\t\treturn best;\n\t}\n\n\t/** A shape's dragged corner moved so its width or height matches another object's, when one is within reach. */\n\tprivate matchShapeSize(targets: VectorSnapTargets, start: Vec2, end: Vec2, fromCentre = false): Vec2 {\n\t\t// Dragged from the centre, the shape is twice as large as the drag.\n\t\tconst k = fromCentre ? 2 : 1;\n\t\tconst tol = this.snapTolerance() * k;\n\t\tconst out = { ...end };\n\t\tconst guides: VectorSnapGuides = { lines: [], labels: [] };\n\t\tfor (const axis of ['x', 'y'] as const) {\n\t\t\tconst d = end[axis] - start[axis];\n\t\t\tconst len = Math.abs(d) > 1e-9 ? targets.matchSize(Math.abs(d) * k, tol) : null;\n\t\t\tif (len === null || len <= 0) continue;\n\t\t\tout[axis] = start[axis] + (Math.sign(d) * len) / k;\n\t\t\tconst from = { ...out, [axis]: fromCentre ? 2 * start[axis] - out[axis] : start[axis] };\n\t\t\tguides.lines.push({ kind: 'size', x1: from.x, y1: from.y, x2: out.x, y2: out.y });\n\t\t\tguides.labels.push({ kind: 'size', x: (from.x + out.x) / 2, y: (from.y + out.y) / 2, text: `${axis === 'x' ? 'W' : 'H'} ${Math.round(len * 100) / 100}` });\n\t\t}\n\t\tthis.setGuides(guides);\n\t\treturn out;\n\t}\n\n\t/** Primary button released. */\n\tpointerUp(screen?: Vec2, input: VectorPointerInput = {}): void {\n\t\t// A pen reports no pressure as it lifts: keep the last pressure for the final move.\n\t\tif (screen && this.drag && this.drag.kind !== 'pan') this.pointerMove(screen, { ...input, pressure: undefined });\n\t\tconst drag = this.drag;\n\t\tthis.drag = null;\n\t\tthis.interaction = null;\n\t\tthis.guides = null;\n\t\tif (!drag) return;\n\t\tswitch (drag.kind) {\n\t\t\tcase 'pan':\n\t\t\tcase 'marquee':\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\tcase 'move':\n\t\t\t\tif (!drag.moved) {\n\t\t\t\t\tif (drag.clickId) this.selection = [drag.clickId];\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn this.commit(drag.duplicated ? 'Duplicate' : 'Move', drag.before);\n\t\t\tcase 'scale':\n\t\t\t\treturn this.commit('Scale', drag.before);\n\t\t\tcase 'rotate':\n\t\t\t\treturn this.commit('Rotate', drag.before);\n\t\t\tcase 'anchors':\n\t\t\t\tif (!drag.moved) {\n\t\t\t\t\tif (drag.clickRef) this.anchors = [drag.clickRef];\n\t\t\t\t\tif (drag.before.doc !== this.doc) return this.commit('Add Point', drag.before);\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn this.commit(drag.before.doc !== drag.base ? 'Add Point' : 'Move Points', drag.before);\n\t\t\tcase 'handle':\n\t\t\t\treturn this.commit('Move Handle', drag.before);\n\t\t\tcase 'bend':\n\t\t\t\tif (!drag.moved) {\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn this.commit('Bend Segment', drag.before);\n\t\t\tcase 'corner': {\n\t\t\t\tif (drag.moved) return this.commit('Corner Radius', drag.before);\n\t\t\t\tif (drag.alt) {\n\t\t\t\t\tconst kind = nextVectorCornerKind(drag.widget.kind);\n\t\t\t\t\tthis.doc = this.updateAnchors(drag.base, drag.refs, (a) => setAnchorCorner(a, a.corner?.radius ?? 0, kind));\n\t\t\t\t\treturn this.commit('Corner Kind', drag.before);\n\t\t\t\t}\n\t\t\t\t// A click selects the widget's corner, so the next drag rounds that corner alone.\n\t\t\t\tconst ref = drag.widget.ref;\n\t\t\t\tconst has = this.anchors.some((a) => sameRef(a, ref));\n\t\t\t\tthis.editingId = ref.nodeId;\n\t\t\t\tthis.anchors = drag.shift ? (has ? this.anchors.filter((a) => !sameRef(a, ref)) : [...this.anchors, ref]) : [ref];\n\t\t\t\tthis.notify();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'width': {\n\t\t\t\tif (drag.moved) {\n\t\t\t\t\tconst several = drag.group.length > 0;\n\t\t\t\t\tif (drag.mode === 'copy') this.widthPoints = [drag.ref];\n\t\t\t\t\telse if (drag.mode === 'move' && several) {\n\t\t\t\t\t\t// Points moved together stay selected (re-sorting may renumber them).\n\t\t\t\t\t\tthis.widthPoints = [drag.source, ...drag.group].map((i) => ({ ...drag.ref, index: drag.renumber?.get(i) ?? i }));\n\t\t\t\t\t} else if (!several) this.widthPoints = [drag.ref];\n\t\t\t\t\tconst label = drag.mode === 'copy' ? 'Copy Width Point' : drag.mode === 'move' ? (several ? 'Move Width Points' : 'Move Width Point') : several ? 'Width Points' : 'Width Point';\n\t\t\t\t\treturn this.commit(label, drag.before);\n\t\t\t\t}\n\t\t\t\t// A click adds nothing; on a width point it selects it (just it, among several selected).\n\t\t\t\tif (drag.created) {\n\t\t\t\t\tthis.doc = drag.before.doc;\n\t\t\t\t\tthis.widthPoints = [];\n\t\t\t\t\tthis.notify(true);\n\t\t\t\t} else {\n\t\t\t\t\tif (!drag.shift && drag.group.length) this.widthPoints = [drag.ref];\n\t\t\t\t\tthis.notify();\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'builder':\n\t\t\t\treturn this.upBuilder(drag);\n\t\t\tcase 'gradient':\n\t\t\t\treturn this.upGradient(drag);\n\t\t\tcase 'stroke': {\n\t\t\t\tthis.notify();\n\t\t\t\tif (drag.tool === 'knife') this.knife(drag.points);\n\t\t\t\telse this.erase(drag.points);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'pull':\n\t\t\t\tif (drag.closing) this.pen = null;\n\t\t\t\treturn this.commit(drag.label, drag.before);\n\t\t\tcase 'shape': {\n\t\t\t\tconst node = this.getNode(drag.nodeId);\n\t\t\t\tconst b = node ? vectorNodeBounds(node) : null;\n\t\t\t\tconst min = 2 / this.viewport.zoom;\n\t\t\t\tif (!b || (b.width < min && b.height < min)) {\n\t\t\t\t\tthis.doc = drag.before.doc;\n\t\t\t\t\tthis.notify(true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.selection = [drag.nodeId];\n\t\t\t\tthis.anchors = [];\n\t\t\t\tthis.tool = 'select';\n\t\t\t\treturn this.commit(`Draw ${TOOL_LABELS[drag.tool]}`, drag.before);\n\t\t\t}\n\t\t\tcase 'pencil': {\n\t\t\t\tconst node = this.getNode(drag.nodeId);\n\t\t\t\tif (node?.type !== 'path' || drag.points.length < 2) {\n\t\t\t\t\tthis.doc = drag.before.doc;\n\t\t\t\t\tthis.notify(true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor (const q of drag.lazy.finish()) {\n\t\t\t\t\tdrag.points.push(q);\n\t\t\t\t\tdrag.pressures?.push(drag.pressure);\n\t\t\t\t}\n\t\t\t\tconst shape = this.editing.recognizeShapes ? recognizeInkShape(drag.points, { minSize: 6 / this.viewport.zoom }) : null;\n\t\t\t\tif (shape) {\n\t\t\t\t\tthis.replaceNode(drag.nodeId, { ...node, name: inkShapeLabel(shape), subpaths: [inkShapeSubpath(shape)] });\n\t\t\t\t\tthis.selection = [drag.nodeId];\n\t\t\t\t\treturn this.commit(`Draw ${inkShapeLabel(shape)}`, drag.before);\n\t\t\t\t}\n\t\t\t\tconst fitted = fitCubics(drag.points, 1.5 / this.viewport.zoom, { cornerAngleDeg: 60 });\n\t\t\t\tconst anchors: VectorAnchor[] = [];\n\t\t\t\tfor (const c of fitted) {\n\t\t\t\t\tif (!anchors.length) anchors.push({ x: c.p0[0], y: c.p0[1] });\n\t\t\t\t\tanchors[anchors.length - 1].out = { x: c.p1[0], y: c.p1[1] };\n\t\t\t\t\tanchors.push({ x: c.p3[0], y: c.p3[1], in: { x: c.p2[0], y: c.p2[1] } });\n\t\t\t\t}\n\t\t\t\tconst subpaths = anchors.length > 1 ? bezierToSubpaths(subpathsToBezier([{ anchors, closed: false }])) : node.subpaths;\n\t\t\t\tconst profile = drag.pressures ? pencilPressureProfile(drag.points, drag.pressures) : [];\n\t\t\t\tthis.replaceNode(drag.nodeId, { ...node, subpaths, ...(profile.length ? { strokeWidthProfile: profile } : {}) });\n\t\t\t\tthis.selection = [drag.nodeId];\n\t\t\t\treturn this.commit('Draw', drag.before);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** The pointer left the canvas. */\n\tpointerLeave(): void {\n\t\tif (this.drag) return;\n\t\tif (!this.hover && !this.pointer && !this.guides) return;\n\t\tthis.hover = null;\n\t\tthis.pointer = null;\n\t\tthis.guides = null;\n\t\tthis.notify();\n\t}\n\n\t/** Abandons the drag in progress (Escape, lost capture). */\n\tcancelDrag(): void {\n\t\tconst drag = this.drag;\n\t\tif (!drag) return;\n\t\tthis.drag = null;\n\t\tthis.interaction = null;\n\t\tthis.guides = null;\n\t\tif (drag.kind === 'pan' || drag.kind === 'builder') {\n\t\t\tif (drag.kind === 'builder') this.builderPreview = null;\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (drag.kind === 'marquee') {\n\t\t\tthis.selection = drag.base;\n\t\t\tthis.anchors = drag.baseAnchors;\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tthis.restore(drag.before);\n\t}\n\n\t/**\n\t * Double click: select tool enters path editing, direct tool toggles an anchor or adds one,\n\t * width tool opens the width-point editor (`widthPointEdit`).\n\t */\n\tdoubleClick(screen: Vec2): void {\n\t\tconst p = this.screenToDocument(screen);\n\t\tif (this.tool === 'select') {\n\t\t\tconst path = this.hitPath(p);\n\t\t\tif (!path) return;\n\t\t\tthis.tool = 'direct';\n\t\t\tthis.selection = [path.id];\n\t\t\tthis.editingId = path.id;\n\t\t\tthis.anchors = [];\n\t\t\tthis.notify();\n\t\t\treturn;\n\t\t}\n\t\tif (this.tool === 'direct') {\n\t\t\tconst anchor = this.hitAnchor(screen, getVisibleAnchors(this.view()));\n\t\t\tif (anchor) {\n\t\t\t\tconst a = getAnchor(this.doc, anchor)!;\n\t\t\t\tthis.convertAnchors(a.in || a.out ? 'corner' : 'smooth', [anchor]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst id = this.hover?.segment?.nodeId ?? this.editingId;\n\t\t\tif (id) {\n\t\t\t\tconst node = this.getNode(id) as VectorPathNode | undefined;\n\t\t\t\tconst seg = node ? nearestSegment(node, this.toLocal(id, p)) : null;\n\t\t\t\tif (seg && seg.distance <= this.tolerance()) this.insertAnchorAt(id, p);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (this.tool === 'width') {\n\t\t\t// Width Point Edit: on a width point, or on a stroke (applying it adds a point there).\n\t\t\tconst hit = this.hitWidthHandle(screen);\n\t\t\tif (hit) {\n\t\t\t\tthis.openWidthPointEdit(hit.ref);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst spot = this.strokeSpotAt(p);\n\t\t\tif (spot) this.openWidthPointEdit({ nodeId: spot.nodeId, subpath: spot.subpath, t: spot.t });\n\t\t\treturn;\n\t\t}\n\t\tif (this.tool === 'pen') this.finishPath();\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Keyboard input\n\t// -------------------------------------------------------------------------\n\n\t/**\n\t * Handles editor shortcuts. Returns true when the key was used (call `preventDefault`).\n\t * Do not forward keys typed into text inputs.\n\t */\n\tkeyDown(input: VectorKeyInput): boolean {\n\t\tconst key = input.key;\n\t\tconst lower = key.length === 1 ? key.toLowerCase() : key;\n\t\tif (key === ' ') {\n\t\t\tif (!this.spaceHeld) {\n\t\t\t\tthis.spaceHeld = true;\n\t\t\t\tthis.notify();\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tif (input.mod) {\n\t\t\tswitch (lower) {\n\t\t\t\tcase 'z':\n\t\t\t\t\tinput.shift ? this.redo() : this.undo();\n\t\t\t\t\treturn true;\n\t\t\t\tcase 'y':\n\t\t\t\t\tthis.redo();\n\t\t\t\t\treturn true;\n\t\t\t\tcase 'a':\n\t\t\t\t\tthis.selectAll();\n\t\t\t\t\treturn true;\n\t\t\t\tcase 'd':\n\t\t\t\t\tthis.duplicate();\n\t\t\t\t\treturn true;\n\t\t\t\tcase 'g':\n\t\t\t\t\tinput.shift ? this.ungroup() : this.group();\n\t\t\t\t\treturn true;\n\t\t\t\tcase 'j':\n\t\t\t\t\treturn input.alt ? this.averageAnchors('both').ok : this.joinPaths().ok;\n\t\t\t\tcase ']':\n\t\t\t\t\tthis.arrange(input.alt ? 'front' : 'forward');\n\t\t\t\t\treturn true;\n\t\t\t\tcase '[':\n\t\t\t\t\tthis.arrange(input.alt ? 'back' : 'backward');\n\t\t\t\t\treturn true;\n\t\t\t\tcase '=':\n\t\t\t\tcase '+':\n\t\t\t\t\tthis.setViewport({ zoom: this.viewport.zoom * 1.25 });\n\t\t\t\t\treturn true;\n\t\t\t\tcase '-':\n\t\t\t\t\tthis.setViewport({ zoom: this.viewport.zoom / 1.25 });\n\t\t\t\t\treturn true;\n\t\t\t\tcase '0':\n\t\t\t\t\tthis.setViewport({ zoom: 1 });\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tswitch (key) {\n\t\t\tcase 'Escape':\n\t\t\t\tif (this.drag) this.cancelDrag();\n\t\t\t\telse if (this.widthEdit) this.closeWidthPointEdit();\n\t\t\t\telse if (this.tool === 'width' && this.widthPoints.length) {\n\t\t\t\t\tthis.widthPoints = [];\n\t\t\t\t\tthis.notify();\n\t\t\t\t}\n\t\t\t\telse if (this.pen) this.finishPath();\n\t\t\t\telse if (this.blendStart) {\n\t\t\t\t\tthis.blendStart = null;\n\t\t\t\t\tthis.notify();\n\t\t\t\t} else if (this.tool === 'direct' && this.anchors.length) {\n\t\t\t\t\tthis.anchors = [];\n\t\t\t\t\tthis.notify();\n\t\t\t\t} else if (this.tool !== 'select') this.setTool('select');\n\t\t\t\telse this.clearSelection();\n\t\t\t\treturn true;\n\t\t\tcase 'Enter':\n\t\t\t\tif (this.pen) {\n\t\t\t\t\tthis.finishPath();\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (this.tool === 'select' && this.selection.length === 1 && this.getNode(this.selection[0])?.type === 'path') {\n\t\t\t\t\tthis.tool = 'direct';\n\t\t\t\t\tthis.editingId = this.selection[0];\n\t\t\t\t\tthis.notify();\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\tcase 'Delete':\n\t\t\tcase 'Backspace':\n\t\t\t\tif (this.tool === 'width' && this.widthPoints.length) return this.removeWidthPoints().ok;\n\t\t\t\tif (this.tool === 'gradient' && this.gradientEdit.stop !== null) return this.removeGradientStop(this.gradientEdit.stop).ok;\n\t\t\t\tif (this.pen) {\n\t\t\t\t\tconst node = this.getNode(this.pen.nodeId) as VectorPathNode;\n\t\t\t\t\tconst sp = node.subpaths[this.pen.subpath];\n\t\t\t\t\tif (sp.anchors.length > 2) this.deleteAnchors([{ nodeId: node.id, subpath: this.pen.subpath, index: sp.anchors.length - 1 }]);\n\t\t\t\t\telse {\n\t\t\t\t\t\tthis.removeNodes([node.id], { label: 'Delete Path' });\n\t\t\t\t\t\tthis.pen = null;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn this.deleteSelection().ok;\n\t\t\tcase 'ArrowLeft':\n\t\t\tcase 'ArrowRight':\n\t\t\tcase 'ArrowUp':\n\t\t\tcase 'ArrowDown': {\n\t\t\t\tconst step = input.shift ? 10 : 1;\n\t\t\t\tconst dx = key === 'ArrowLeft' ? -step : key === 'ArrowRight' ? step : 0;\n\t\t\t\tconst dy = key === 'ArrowUp' ? -step : key === 'ArrowDown' ? step : 0;\n\t\t\t\treturn this.translate(dx, dy).ok;\n\t\t\t}\n\t\t}\n\t\tif (key.length === 1 && !input.alt) {\n\t\t\tconst tool = TOOL_KEYS[lower];\n\t\t\tif (tool) {\n\t\t\t\tthis.setTool(tool);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tkeyUp(input: VectorKeyInput): void {\n\t\tif (input.key === ' ' && this.spaceHeld) {\n\t\t\tthis.spaceHeld = false;\n\t\t\tthis.notify();\n\t\t}\n\t}\n}\n\n/** \"Rectangle\", \"Square\", \"Ellipse\", \"Circle\", \"Line\", \"Triangle\" or \"Polygon\". */\nfunction inkShapeLabel(shape: VectorInkShape): string {\n\tconst name = vectorInkShapeName(shape);\n\treturn name[0].toUpperCase() + name.slice(1);\n}\n\n/** The subpath of a recognised pencil shape. */\nfunction inkShapeSubpath(shape: VectorInkShape): VectorSubpath {\n\tswitch (shape.kind) {\n\t\tcase 'line':\n\t\t\treturn lineSubpath({ x: shape.a[0], y: shape.a[1] }, { x: shape.b[0], y: shape.b[1] });\n\t\tcase 'rect':\n\t\t\treturn rectSubpath(shape);\n\t\tcase 'ellipse':\n\t\t\treturn ellipseSubpath(shape);\n\t\tcase 'polygon':\n\t\t\treturn { anchors: shape.points.map(([x, y]) => ({ x, y })), closed: true };\n\t}\n}\n\n/** Snaps `p` to the nearest 45° direction from `origin`, keeping its distance. */\nfunction constrain45(origin: Vec2, p: Vec2): Vec2 {\n\tconst dx = p.x - origin.x;\n\tconst dy = p.y - origin.y;\n\tconst len = Math.hypot(dx, dy);\n\tconst a = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);\n\treturn { x: origin.x + Math.cos(a) * len, y: origin.y + Math.sin(a) * len };\n}\n\n/** Scale factors and origin for dragging a selection-box handle to `p`. */\nexport function scaleFor(handle: VectorTransformHandle, b: VectorBounds, p: Vec2, mods: VectorModifiers = {}): { sx: number; sy: number; origin: Vec2 } {\n\tconst west = handle.includes('w');\n\tconst east = handle.includes('e');\n\tconst north = handle.startsWith('n');\n\tconst south = handle.startsWith('s');\n\tconst center = { x: b.x + b.width / 2, y: b.y + b.height / 2 };\n\tconst origin = mods.alt ? center : { x: west ? b.x + b.width : east ? b.x : center.x, y: north ? b.y + b.height : south ? b.y : center.y };\n\tconst reachX = mods.alt ? b.width / 2 : b.width;\n\tconst reachY = mods.alt ? b.height / 2 : b.height;\n\tlet sx = west || east ? (reachX > 1e-9 ? ((p.x - origin.x) * (west ? -1 : 1)) / reachX : 1) : 1;\n\tlet sy = north || south ? (reachY > 1e-9 ? ((p.y - origin.y) * (north ? -1 : 1)) / reachY : 1) : 1;\n\tif (mods.shift) {\n\t\tif ((west || east) && (north || south)) {\n\t\t\tconst s = Math.max(Math.abs(sx), Math.abs(sy));\n\t\t\tsx = Math.sign(sx || 1) * s;\n\t\t\tsy = Math.sign(sy || 1) * s;\n\t\t} else if (west || east) sy = Math.abs(sx);\n\t\telse sx = Math.abs(sy);\n\t}\n\treturn { sx, sy, origin };\n}\n\n/** A style applied to a path; a `null` (or empty) width profile removes it. */\nfunction withStyle(node: VectorPathNode, style: VectorStyle & { opacity?: number }): VectorNode {\n\tconst next = { ...node, ...style } as VectorPathNode;\n\tif ('strokeWidthProfile' in style) {\n\t\tconst p = style.strokeWidthProfile ? sanitizeWidthProfile(style.strokeWidthProfile) : [];\n\t\tif (p.length) next.strokeWidthProfile = p;\n\t\telse delete next.strokeWidthProfile;\n\t}\n\treturn next;\n}\n\n/** True when node `id` or one of its ancestors is locked or hidden. */\nfunction hiddenOrLocked(nodes: readonly VectorNode[], id: string): boolean {\n\tconst loc = locateVectorNode(nodes as VectorNode[], id);\n\tif (!loc) return false;\n\tconst n = loc.list[loc.index];\n\tif (n.locked || n.visible === false) return true;\n\treturn loc.parent ? hiddenOrLocked(nodes, loc.parent.id) : false;\n}\n\n/** The width profile of pencil samples and their pressures (positions by length along the samples). */\nfunction pencilPressureProfile(points: readonly Point[], pressures: readonly number[]): VectorWidthPoint[] {\n\tconst n = Math.min(points.length, pressures.length);\n\tif (n < 2) return [];\n\tconst s = [0];\n\tfor (let i = 1; i < n; i++) s.push(s[i - 1] + Math.hypot(points[i][0] - points[i - 1][0], points[i][1] - points[i - 1][1]));\n\tconst total = s[n - 1];\n\tif (!(total > 0)) return [];\n\treturn pressureWidthProfile(s.map((d, i) => ({ t: d / total, pressure: pressures[i] })));\n}\n","/**\n * Framework-neutral render data for a vector editor: the scene (document-space SVG paths and\n * gradient defs) and the overlay (screen-space selection box, anchors, handles, marquee and\n * pen preview). React, Svelte or plain DOM code turns these into `<svg>` elements.\n */\nimport { bezierPathToSvg, transformBezierPath, type BezierPath } from '../canvas/pathops/bezier.js';\nimport { hatchPath } from '../canvas/pathops/hatch.js';\nimport type { SvgGradientDef } from '../canvas/gradient.js';\nimport { expandVectorGradientStops, isCentredVectorGradient, isVectorGradient, parseVectorColor, sortedVectorGradientStops, type VectorGradientHandle, type VectorPaintDef } from './gradient.js';\nimport { getVectorGradientAnnotator, VECTOR_GRADIENT_STOP_GAP } from './gradientTool.js';\n\nexport type { SvgGradientDef };\nimport {\n\tgetAnchorWorld,\n\tgetVectorCornerWidgets,\n\tgetVectorSelectionBoxGeometry,\n\tgetVectorWidthHandles,\n\tgetVisibleAnchors,\n\tgetVisibleHandles,\n\ttype VectorEditorController,\n\ttype VectorEditorState,\n\ttype VectorSelectionBoxGeometry,\n\ttype VectorWidthPointRef\n} from './controller.js';\nimport { sanitizeWidthProfile, widthProfilePresetId, type VectorWidthPreset } from './width.js';\nimport {\n\tfindVectorNode,\n\tlocateVectorNode,\n\tsubpathsToBezier,\n\tvectorWorldMatrix,\n\tIDENTITY_MATRIX,\n\tinferAnchorType,\n\tmultiplyMatrices,\n\tnodeMatrix,\n\tvectorNodeBounds,\n\tvectorParentMatrix,\n\tvectorPathData,\n\tvectorPathWorldBezier,\n\ttype AffineMatrix\n} from './geometry.js';\nimport { vectorPaintToSvg, vectorStrokeSvgParts, type VectorRenderStrokeParts } from './svg.js';\nimport { vectorCorners } from './corners.js';\nimport {\n\texpandVectorAppearance,\n\thasOwnVectorAppearance,\n\thasVectorAppearance,\n\tvectorAppearanceOutline,\n\tvectorEnteredGroups,\n\tVECTOR_EFFECT_LABELS,\n\tVECTOR_EFFECT_PARAM_SPECS,\n\ttype VectorEffectParamSpec\n} from './appearance.js';\nimport { VECTOR_EFFECT_TYPES, type VectorEffectType } from './types.js';\nimport { isVectorBlendGroup, resolveVectorBlendOptions, vectorBlendRailAt, vectorBlendSpine } from './blendGroup.js';\nimport type { VectorGuideKind, VectorSnapKind } from './snap.js';\nimport type {\n\tVectorAnchorRef,\n\tVectorAnchorType,\n\tVectorArrowAlign,\n\tVectorArrowhead,\n\tVectorBlendOptions,\n\tVectorBlendSpacing,\n\tVectorBounds,\n\tVectorCornerKind,\n\tVectorGradient,\n\tVectorGradientInterpolation,\n\tVectorGradientSpread,\n\tVectorGradientStop,\n\tVectorGradientType,\n\tVectorStrokeGradientMode,\n\tVectorGroupNode,\n\tVectorHandleRef,\n\tVectorNode,\n\tVectorPaint,\n\tVectorPathfinderOperation,\n\tVectorPathNode,\n\tVectorStrokeAlign,\n\tVectorTransformHandle,\n\tVectorWidthPoint,\n\tVec2\n} from './types.js';\n\nexport interface VectorRenderPath {\n\tkind: 'path';\n\tid: string;\n\t/** SVG `transform` (`matrix(…)`) for a rotated node. */\n\ttransform?: string;\n\td: string;\n\t/** `none`, a colour, or `url(#id)` of a gradient in `defs`. */\n\tfill: string;\n\tstroke: string;\n\tstrokeWidth: number;\n\tstrokeLinecap?: string;\n\tstrokeLinejoin?: string;\n\tstrokeDasharray?: string;\n\tfillRule: 'nonzero' | 'evenodd';\n\topacity: number;\n\t/**\n\t * Set when the stroke takes more than one element: an inside / outside alignment or\n\t * arrowheads. Draw `d` with the fill only, then `stroke.d` with the stroke (at\n\t * `stroke.width`, clipped or masked as `stroke.clip` says), then each head filled with\n\t * `stroke`; group them under `id`, `transform` and `opacity`.\n\t */\n\tstrokeParts?: VectorRenderStrokeParts;\n}\n\nexport interface VectorRenderGroup {\n\tkind: 'group';\n\tid: string;\n\t/** SVG `transform` (`matrix(…)`) for a rotated group. */\n\ttransform?: string;\n\topacity: number;\n\tchildren: VectorRenderNode[];\n}\n\nexport type VectorRenderNode = VectorRenderPath | VectorRenderGroup;\n\nexport interface VectorSceneRenderData {\n\t/** SVG `transform` for the document layer: `translate(x y) scale(zoom)`. */\n\ttransform: string;\n\twidth: number;\n\theight: number;\n\tbackground?: string;\n\t/**\n\t * Paint servers referenced by `fill` / `stroke` values: `<linearGradient>` /\n\t * `<radialGradient>` descriptors and `pattern`s (conic, diamond and stroke-mode gradients:\n\t * draw each of `gradients`, then each tile filled, inside a `<pattern>`).\n\t */\n\tdefs: VectorPaintDef[];\n\t/** Visible nodes, bottom to top. */\n\tnodes: VectorRenderNode[];\n}\n\nexport interface VectorRenderAnchor {\n\tref: VectorAnchorRef;\n\t/** Screen position. */\n\tx: number;\n\ty: number;\n\ttype: VectorAnchorType;\n\tselected: boolean;\n\thovered: boolean;\n\t/** The pen tool closes the path when this anchor is clicked. */\n\tcloses: boolean;\n}\n\nexport interface VectorRenderHandle {\n\tref: VectorHandleRef;\n\tx: number;\n\ty: number;\n\t/** Screen position of the handle's anchor (draw a line to it). */\n\tanchorX: number;\n\tanchorY: number;\n\thovered: boolean;\n}\n\n/** A live corner widget in screen pixels. */\nexport interface VectorRenderCornerWidget {\n\tref: VectorAnchorRef;\n\tx: number;\n\ty: number;\n\tradius: number;\n\tkind: VectorCornerKind;\n\thovered: boolean;\n\t/** Its corners are being dragged. */\n\tactive: boolean;\n}\n\n/**\n * A width point of the width tool in screen pixels: a diamond at (`x`, `y`) on the path and a\n * line across the stroke from `left` to `right`, with a handle at each end.\n */\nexport interface VectorRenderWidthPoint {\n\tref: VectorWidthPointRef;\n\tx: number;\n\ty: number;\n\tleft: Vec2;\n\tright: Vec2;\n\tselected: boolean;\n\t/** The part under the pointer, if any. */\n\thovered: 'center' | 'left' | 'right' | null;\n\t/** Being dragged. */\n\tactive: boolean;\n}\n\n/** A smart guide line in screen pixels. */\nexport interface VectorRenderGuideLine {\n\tkind: VectorGuideKind;\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n}\n\n/** A smart guide label (\"anchor\", \"intersect\", \"align\", \"centre\", a gap or a size) at a screen position. */\nexport interface VectorRenderGuideLabel {\n\tkind: VectorSnapKind;\n\tx: number;\n\ty: number;\n\ttext: string;\n}\n\nexport interface VectorOverlayRenderData {\n\t/** Outlines (screen-space path data) of hovered and selected nodes. */\n\toutlines: { id: string; d: string; selected: boolean }[];\n\t/**\n\t * Selection box with resize and rotate handles (select tool). `x / y / width / height` and\n\t * each handle's `x / y` are the unrotated box in screen pixels: draw them inside\n\t * `<g transform={transform}>`. `corners` and each handle's `screen` are true positions.\n\t */\n\tselectionBox:\n\t\t| (VectorBounds & { angle: number; transform: string; corners: Vec2[]; handles: VectorSelectionBoxGeometry['handles'] })\n\t\t| null;\n\tanchors: VectorRenderAnchor[];\n\thandles: VectorRenderHandle[];\n\t/** Marquee rectangle, screen space. */\n\tmarquee: VectorBounds | null;\n\t/** Rubber-band segment from the pen path's last anchor to the pointer, screen space. */\n\tpenPreview: string | null;\n\t/** Artboard rectangle, screen space. */\n\tartboard: VectorBounds;\n\t/** Smart guides of the snap in progress, screen space (empty when nothing snapped). */\n\tguides: { lines: VectorRenderGuideLine[]; labels: VectorRenderGuideLabel[] };\n\t/**\n\t * The knife or eraser stroke being drawn, screen space: `d` is an open polyline; draw the\n\t * eraser's with a round-capped stroke `width` pixels wide (0 for the knife: a thin line).\n\t */\n\ttoolStroke: { tool: 'knife' | 'eraser'; d: string; width: number } | null;\n\t/** The pencil's lazy-mouse string from the pen to the pointer, screen space. */\n\tpencilString: { x1: number; y1: number; x2: number; y2: number } | null;\n\t/** Where the scissors would cut (screen space). */\n\tcutPoint: Vec2 | null;\n\t/**\n\t * Live corner widgets of the direct tool (screen space): drag one to round its corners,\n\t * Alt-click to cycle round / inverted / chamfer, click to pick its corner alone.\n\t */\n\tcornerWidgets: VectorRenderCornerWidget[];\n\t/** Width points of the width tool (screen space). */\n\twidthPoints: VectorRenderWidthPoint[];\n\t/**\n\t * Where a width-tool drag would add a width point (screen space): draw a dashed line from\n\t * `left` to `right` and a hollow diamond at (`x`, `y`).\n\t */\n\twidthSpot: { x: number; y: number; left: Vec2; right: Vec2 } | null;\n\t/**\n\t * Shape Builder and paint bucket highlight (screen space): `faces` are filled paths (fill\n\t * them translucent and outline them), `hatches` the 45° hatch lines over each face\n\t * (`hatchPath`, a few pixels apart; merge and erase only, empty strings for the paint\n\t * bucket: stroke them over the translucent fill), `lines` are the line pieces and outline\n\t * edges an erase deletes, `trail` is the drag's polyline. `mode` is `merge`, `erase` (Alt)\n\t * or `fill` (paint bucket).\n\t */\n\tbuilder: { mode: 'merge' | 'erase' | 'fill'; faces: string[]; hatches: string[]; lines: string[]; trail: string | null } | null;\n\t/**\n\t * Blends (screen space): the spine of each selected blend (`d`, draw it dashed; `keys` are\n\t * where its keys sit on it; `replaced` when it is a replaced spine) and, with the Blend tool,\n\t * the outline of the shape it blends from (`start`, with the clicked anchor) and the anchors\n\t * of the first subpath under the pointer (click one to start the blend there).\n\t */\n\tblend: {\n\t\tspines: { id: string; d: string; keys: Vec2[]; replaced: boolean }[];\n\t\tstart: { d: string; anchor: Vec2 | null } | null;\n\t\tanchors: { x: number; y: number; hovered: boolean }[];\n\t};\n\t/** The gradient tool's annotator over the selection's gradient (screen space), or null. */\n\tgradient: VectorRenderGradientAnnotator | null;\n\t/** A short status, e.g. \"W 120 × H 80\" or \"45°\". */\n\tstatus: string | null;\n}\n\n/**\n * The gradient annotator in screen pixels: draw the `extent` outline (dashed), the bar from\n * `start` to `end`, the handles, each stop's chip (a swatch at `x, y` with a tick to `barX, barY`;\n * `removing`: the dragged stop is off the bar and will be deleted, draw it faded) and the\n * midpoint diamonds.\n */\nexport interface VectorRenderGradientAnnotator {\n\tnodeId: string;\n\ttype: VectorGradientType;\n\tstart: { x: number; y: number; hovered: boolean };\n\t/** `radius` for radial, conic and diamond gradients. */\n\tend: { x: number; y: number; hovered: boolean; handle: 'end' | 'radius' };\n\taspect: { x: number; y: number; hovered: boolean } | null;\n\tfocal: { x: number; y: number; hovered: boolean } | null;\n\tstops: { index: number; x: number; y: number; barX: number; barY: number; color: string; opacity: number; selected: boolean; hovered: boolean }[];\n\tmidpoints: { index: number; x: number; y: number; hovered: boolean }[];\n\textent: string | null;\n\tremoving: boolean;\n}\n\nconst fmt = (n: number) => Math.round(n * 100) / 100;\nconst fmtMatrix = (m: AffineMatrix) => `matrix(${m.map((n) => Math.round(n * 1e6) / 1e6).join(' ')})`;\n/** Distance between the Shape Builder's hatch lines over a face (screen pixels). */\nexport const VECTOR_BUILDER_HATCH_GAP = 6;\n\nfunction renderNode(stored: VectorNode, defs: VectorPaintDef[], idPrefix: string): VectorRenderNode | null {\n\tif (stored.visible === false) return null;\n\t// Path effects and live booleans draw as their expanded shapes.\n\tconst node = expandVectorAppearance(stored);\n\tif (node.type === 'group') {\n\t\tconst children = node.children.map((c) => renderNode(c, defs, idPrefix)).filter((c): c is VectorRenderNode => !!c);\n\t\treturn { kind: 'group', id: node.id, ...(node.transform ? { transform: fmtMatrix(node.transform) } : {}), opacity: node.opacity ?? 1, children };\n\t}\n\tconst fill = vectorPaintToSvg(node.fill, `${idPrefix}fill-${node.id}`, { node });\n\tconst stroke = vectorPaintToSvg(node.stroke, `${idPrefix}stroke-${node.id}`, { node, role: 'stroke' });\n\tdefs.push(...fill.defs, ...stroke.defs);\n\tconst strokeParts = stroke.value !== 'none' ? vectorStrokeSvgParts(node, `${idPrefix}clip-${node.id}`) : undefined;\n\treturn {\n\t\tkind: 'path',\n\t\tid: node.id,\n\t\t...(node.transform ? { transform: fmtMatrix(node.transform) } : {}),\n\t\t...(strokeParts ? { strokeParts } : {}),\n\t\td: vectorPathData(node),\n\t\tfill: fill.value,\n\t\tstroke: stroke.value,\n\t\tstrokeWidth: node.strokeWidth ?? 1,\n\t\t...(node.strokeLinecap ? { strokeLinecap: node.strokeLinecap } : {}),\n\t\t...(node.strokeLinejoin ? { strokeLinejoin: node.strokeLinejoin } : {}),\n\t\t...(node.strokeDasharray?.length ? { strokeDasharray: node.strokeDasharray.join(' ') } : {}),\n\t\tfillRule: node.fillRule ?? 'nonzero',\n\t\topacity: node.opacity ?? 1\n\t};\n}\n\n/**\n * The document layer. Render the nodes inside `<g transform={transform}>` and `defs` in a\n * `<defs>` block. Pass a unique `idPrefix` when several editors share a page.\n */\nexport function getVectorSceneRenderData(state: Pick<VectorEditorState, 'document' | 'viewport'>, options: { idPrefix?: string } = {}): VectorSceneRenderData {\n\tconst defs: VectorPaintDef[] = [];\n\tconst prefix = options.idPrefix ?? 've-';\n\tconst nodes = state.document.nodes.map((n) => renderNode(n, defs, prefix)).filter((n): n is VectorRenderNode => !!n);\n\tconst v = state.viewport;\n\treturn {\n\t\ttransform: `translate(${fmt(v.x)} ${fmt(v.y)}) scale(${v.zoom})`,\n\t\twidth: state.document.width,\n\t\theight: state.document.height,\n\t\t...(state.document.background ? { background: state.document.background } : {}),\n\t\tdefs,\n\t\tnodes\n\t};\n}\n\nconst toScreen = (state: Pick<VectorEditorState, 'viewport'>, p: Vec2) => ({ x: p.x * state.viewport.zoom + state.viewport.x, y: p.y * state.viewport.zoom + state.viewport.y });\n\nfunction screenOutline(state: VectorEditorState, node: VectorNode, entered: ReadonlySet<string>): string {\n\tconst v = state.viewport;\n\tconst view: AffineMatrix = [v.zoom, 0, 0, v.zoom, v.x, v.y];\n\tconst parent = vectorParentMatrix(state.document.nodes, node.id) ?? IDENTITY_MATRIX;\n\treturn outlinePaths(node, parent, entered)\n\t\t.map((path) => bezierPathToSvg(transformBezierPath(path, view), { precision: 2 }))\n\t\t.join(' ');\n}\n\n/**\n * Document-space outlines of a subtree: paths as stored (their anchors are what is edited; the\n * scene shows their effects), live groups as drawn unless they are being edited (then their\n * children, as figcraft outlines a boolean group's operands).\n */\nfunction outlinePaths(node: VectorNode, parent: AffineMatrix, entered: ReadonlySet<string>): BezierPath[] {\n\tif (node.type === 'path') return [vectorPathWorldBezier(node, parent, { effects: false })];\n\tconst m = multiplyMatrices(nodeMatrix(node), parent);\n\tif (hasOwnVectorAppearance(node) && !entered.has(node.id)) return [transformBezierPath(vectorAppearanceOutline(node), m)];\n\treturn node.children.filter((c) => c.visible !== false).flatMap((c) => outlinePaths(c, m, entered));\n}\n\n/** Live groups being edited in `state` (see `vectorEnteredGroups`). */\nexport function getVectorEnteredGroups(state: Pick<VectorEditorState, 'document' | 'selection' | 'editingId'>): Set<string> {\n\treturn vectorEnteredGroups(state.document.nodes, state.editingId ? [...state.selection, state.editingId] : state.selection);\n}\n\nfunction collect(node: VectorNode): VectorPathNode[] {\n\tif (node.type === 'path') return [node];\n\treturn node.children.flatMap(collect);\n}\n\n/** The editing overlay in screen pixels. Render it on top of the scene, untransformed. */\nexport function getVectorOverlayRenderData(state: VectorEditorState): VectorOverlayRenderData {\n\tconst doc = state.document;\n\tconst z = state.viewport.zoom;\n\tconst outlines: VectorOverlayRenderData['outlines'] = [];\n\tconst entered = getVectorEnteredGroups(state);\n\tconst seen = new Set<string>();\n\tconst outline = (id: string, selected: boolean) => {\n\t\tif (seen.has(id)) return;\n\t\tconst node = findVectorNode(doc.nodes, id);\n\t\tif (!node || node.visible === false) return;\n\t\tseen.add(id);\n\t\toutlines.push({ id, d: screenOutline(state, node, entered), selected });\n\t};\n\tfor (const id of state.selection) outline(id, true);\n\tif (state.editingId) outline(state.editingId, true);\n\tif (state.hover?.nodeId && !state.interaction) outline(state.hover.nodeId, false);\n\n\tlet selectionBox: VectorOverlayRenderData['selectionBox'] = null;\n\tif (state.tool === 'select' && state.selectionFrame && state.interaction?.type !== 'marquee') {\n\t\tconst g = getVectorSelectionBoxGeometry(state.selectionFrame, state.viewport);\n\t\tselectionBox = { ...g.box, angle: g.angle, transform: g.transform, corners: g.corners, handles: g.handles };\n\t}\n\n\tconst selected = (r: VectorAnchorRef) => state.selectedAnchors.some((a) => a.nodeId === r.nodeId && a.subpath === r.subpath && a.index === r.index);\n\tconst sameAnchor = (a: VectorAnchorRef | undefined, b: VectorAnchorRef) => !!a && a.nodeId === b.nodeId && a.subpath === b.subpath && a.index === b.index;\n\tconst anchors: VectorRenderAnchor[] = getVisibleAnchors(state).map((ref) => {\n\t\tconst a = getAnchorWorld(doc, ref)!;\n\t\tconst s = toScreen(state, a);\n\t\tconst closes = !!state.pen && state.pen.nodeId === ref.nodeId && state.pen.subpath === ref.subpath && ref.index === 0;\n\t\treturn { ref, x: s.x, y: s.y, type: a.type ?? inferAnchorType(a), selected: selected(ref), hovered: sameAnchor(state.hover?.anchor, ref), closes };\n\t});\n\tif (state.tool === 'pen' && !state.pen && state.hover?.anchor) {\n\t\tconst a = getAnchorWorld(doc, state.hover.anchor);\n\t\tif (a) {\n\t\t\tconst s = toScreen(state, a);\n\t\t\tanchors.push({ ref: state.hover.anchor, x: s.x, y: s.y, type: a.type ?? 'corner', selected: false, hovered: true, closes: false });\n\t\t}\n\t}\n\n\tconst handles: VectorRenderHandle[] = getVisibleHandles(state).flatMap((ref) => {\n\t\tconst a = getAnchorWorld(doc, ref);\n\t\tconst h = a?.[ref.handle];\n\t\tif (!a || !h) return [];\n\t\tconst s = toScreen(state, h);\n\t\tconst sa = toScreen(state, a);\n\t\tconst hovered = !!state.hover?.handle && sameAnchor(state.hover.handle, ref) && state.hover.handle.handle === ref.handle;\n\t\treturn [{ ref, x: s.x, y: s.y, anchorX: sa.x, anchorY: sa.y, hovered }];\n\t});\n\n\tlet marquee: VectorBounds | null = null;\n\tif (state.interaction?.type === 'marquee') {\n\t\tconst b = state.interaction.bounds;\n\t\tconst tl = toScreen(state, b);\n\t\tmarquee = { x: tl.x, y: tl.y, width: b.width * z, height: b.height * z };\n\t}\n\n\tlet penPreview: string | null = null;\n\tif (state.tool === 'pen' && state.pen && state.pointer && !state.interaction) {\n\t\tconst node = findVectorNode(doc.nodes, state.pen.nodeId);\n\t\tconst sp = node?.type === 'path' ? node.subpaths[state.pen.subpath] : undefined;\n\t\tconst last = sp ? getAnchorWorld(doc, { nodeId: state.pen.nodeId, subpath: state.pen.subpath, index: sp.anchors.length - 1 }) : undefined;\n\t\tif (last) {\n\t\t\tconst a = toScreen(state, last);\n\t\t\tconst p = toScreen(state, state.pointer);\n\t\t\tconst c = last.out ? toScreen(state, last.out) : null;\n\t\t\tpenPreview = c ? `M${fmt(a.x)} ${fmt(a.y)}Q${fmt(c.x)} ${fmt(c.y)} ${fmt(p.x)} ${fmt(p.y)}` : `M${fmt(a.x)} ${fmt(a.y)}L${fmt(p.x)} ${fmt(p.y)}`;\n\t\t}\n\t}\n\n\tconst origin = toScreen(state, { x: 0, y: 0 });\n\tconst artboard = { x: origin.x, y: origin.y, width: doc.width * z, height: doc.height * z };\n\n\tlet status: string | null = null;\n\tconst it = state.interaction;\n\tif (it?.type === 'rotate') status = `${Math.round(it.angle)}°`;\n\telse if (it?.type === 'move' || it?.type === 'anchors') status = `Δ ${fmt(it.dx)}, ${fmt(it.dy)}`;\n\telse if (it?.type === 'scale' || it?.type === 'draw') {\n\t\tconst b = it.type === 'draw' ? (findVectorNode(doc.nodes, it.nodeId) ? vectorNodeBounds(findVectorNode(doc.nodes, it.nodeId)!) : null) : state.selectionFrame;\n\t\tif (b) status = `W ${fmt(b.width)} × H ${fmt(b.height)}`;\n\t}\n\n\tconst guides: VectorOverlayRenderData['guides'] = { lines: [], labels: [] };\n\tif (state.guides) {\n\t\tfor (const l of state.guides.lines) {\n\t\t\tconst a = toScreen(state, { x: l.x1, y: l.y1 });\n\t\t\tconst b = toScreen(state, { x: l.x2, y: l.y2 });\n\t\t\tguides.lines.push({ kind: l.kind, x1: fmt(a.x), y1: fmt(a.y), x2: fmt(b.x), y2: fmt(b.y) });\n\t\t}\n\t\tfor (const l of state.guides.labels) {\n\t\t\tconst p = toScreen(state, l);\n\t\t\tguides.labels.push({ kind: l.kind, x: fmt(p.x), y: fmt(p.y), text: l.text });\n\t\t}\n\t}\n\n\tlet toolStroke: VectorOverlayRenderData['toolStroke'] = null;\n\tif (it?.type === 'stroke') {\n\t\tconst pts = it.points.map((q) => toScreen(state, q));\n\t\tconst d = pts.map((q, i) => `${i ? 'L' : 'M'}${fmt(q.x)} ${fmt(q.y)}`).join('') + (pts.length === 1 ? `L${fmt(pts[0].x)} ${fmt(pts[0].y)}` : '');\n\t\ttoolStroke = { tool: it.tool, d, width: fmt(it.width * z) };\n\t}\n\tlet pencilString: VectorOverlayRenderData['pencilString'] = null;\n\tif (it?.type === 'draw' && it.string) {\n\t\tconst a = toScreen(state, it.string[0]);\n\t\tconst b = toScreen(state, it.string[1]);\n\t\tpencilString = { x1: fmt(a.x), y1: fmt(a.y), x2: fmt(b.x), y2: fmt(b.y) };\n\t}\n\tconst dragged = it?.type === 'corner' ? it.ref : null;\n\tconst cornerWidgets: VectorRenderCornerWidget[] = getVectorCornerWidgets(state).map((w) => {\n\t\tconst s = toScreen(state, w);\n\t\treturn { ref: w.ref, x: fmt(s.x), y: fmt(s.y), radius: w.radius, kind: w.kind, hovered: sameAnchor(state.hover?.corner, w.ref), active: !!dragged };\n\t});\n\tif (it?.type === 'corner') status = `R ${fmt(it.radius)}`;\n\tconst cutPoint = state.tool === 'scissors' && state.hover?.segment && !it ? (({ x, y }) => ({ x: fmt(x), y: fmt(y) }))(toScreen(state, state.hover.segment.point)) : null;\n\n\tconst sc = (p: Vec2) => {\n\t\tconst q = toScreen(state, p);\n\t\treturn { x: fmt(q.x), y: fmt(q.y) };\n\t};\n\tconst sameWidth = (a: VectorWidthPointRef | null | undefined, b: VectorWidthPointRef) => !!a && a.nodeId === b.nodeId && a.index === b.index;\n\tconst widthPoints: VectorRenderWidthPoint[] = getVectorWidthHandles(state).map((h) => {\n\t\tconst c = sc(h);\n\t\tconst hw = state.hover?.widthPoint;\n\t\tconst hovered = hw && sameWidth(hw, h.ref) && hw.subpath === h.ref.subpath ? hw.part : null;\n\t\treturn { ref: h.ref, x: c.x, y: c.y, left: sc(h.left), right: sc(h.right), selected: h.selected, hovered, active: it?.type === 'width' && sameWidth(it.ref, h.ref) };\n\t});\n\tconst spot = state.tool === 'width' && !it ? state.hover?.strokeSpot : null;\n\tconst widthSpot = spot ? { ...sc(spot.point), left: sc(spot.left), right: sc(spot.right) } : null;\n\tif (it?.type === 'width') status = it.mode === 'move' ? 'Move width point' : it.mode === 'copy' ? 'Copy width point' : `W ${fmt(it.width)}`;\n\n\tlet builder: VectorOverlayRenderData['builder'] = null;\n\tif (state.builder) {\n\t\tconst v = state.viewport;\n\t\tconst view: AffineMatrix = [v.zoom, 0, 0, v.zoom, v.x, v.y];\n\t\tconst svg = (path: BezierPath) => bezierPathToSvg(path, { precision: 2 });\n\t\tconst screen = state.builder.faces.map((f) => transformBezierPath(f, view));\n\t\tconst hatch = state.builder.mode !== 'fill';\n\t\tconst trail = state.builder.trail && state.builder.trail.length > 1 ? state.builder.trail.map((q, i) => `${i ? 'L' : 'M'}${sc(q).x} ${sc(q).y}`).join('') : null;\n\t\tbuilder = {\n\t\t\tmode: state.builder.mode,\n\t\t\tfaces: screen.map(svg),\n\t\t\thatches: screen.map((f) => (hatch ? svg(hatchPath(f, { gap: VECTOR_BUILDER_HATCH_GAP })) : '')),\n\t\t\tlines: state.builder.lines.map((l) => svg(transformBezierPath(l, view))),\n\t\t\ttrail\n\t\t};\n\t\tif (state.builder.mode !== 'fill' && it?.type === 'builder') {\n\t\t\tconst n = state.builder.faces.length;\n\t\t\tstatus = state.builder.mode === 'erase' ? `Delete ${n} ${n === 1 ? 'face' : 'faces'}` : `Merge ${n} ${n === 1 ? 'face' : 'faces'}`;\n\t\t}\n\t}\n\n\tconst blend = blendOverlay(state);\n\tif (state.tool === 'blend' && !it) status = state.blendStart ? 'Click another shape to blend into' : 'Click a shape to blend from';\n\n\tlet gradient: VectorRenderGradientAnnotator | null = null;\n\tif (state.tool === 'gradient') {\n\t\tgradient = gradientOverlay(state);\n\t\tif (!gradient && !it) status = 'Drag across a shape to set a gradient';\n\t\telse if (it?.type === 'gradient' && it.removing) status = 'Release to delete the stop';\n\t}\n\n\treturn { outlines, selectionBox, anchors, handles, marquee, penPreview, artboard, guides, status, toolStroke, pencilString, cutPoint, cornerWidgets, widthPoints, widthSpot, builder, blend, gradient };\n}\n\nfunction gradientOverlay(state: VectorEditorState): VectorRenderGradientAnnotator | null {\n\tconst a = getVectorGradientAnnotator(state.document, state.selection, state.viewport, state.gradientEdit.target);\n\tif (!a) return null;\n\tconst hover = state.interaction ? null : state.hover?.gradient;\n\tconst handleHovered = (h: VectorGradientHandle) => hover?.kind === 'handle' && hover.handle === h;\n\tconst pt = (p: Vec2) => ({ x: fmt(p.x), y: fmt(p.y) });\n\tconst centred = isCentredVectorGradient(a.gradient.type);\n\tconst removing = state.interaction?.type === 'gradient' && !!state.interaction.removing;\n\treturn {\n\t\tnodeId: a.nodeId,\n\t\ttype: a.gradient.type,\n\t\tstart: { ...pt(a.start), hovered: handleHovered('start') },\n\t\tend: { ...pt(a.end), hovered: handleHovered(centred ? 'radius' : 'end'), handle: centred ? 'radius' : 'end' },\n\t\taspect: a.aspect ? { ...pt(a.aspect), hovered: handleHovered('aspect') } : null,\n\t\tfocal: a.focal ? { ...pt(a.focal), hovered: handleHovered('focal') } : null,\n\t\tstops: a.stops.map((s) => ({\n\t\t\tindex: s.index,\n\t\t\t...pt(s),\n\t\t\tbarX: fmt(s.x - a.normal.x * VECTOR_GRADIENT_STOP_GAP),\n\t\t\tbarY: fmt(s.y - a.normal.y * VECTOR_GRADIENT_STOP_GAP),\n\t\t\tcolor: s.color,\n\t\t\topacity: s.opacity ?? 1,\n\t\t\tselected: state.gradientEdit.stop === s.index,\n\t\t\thovered: hover?.kind === 'stop' && hover.index === s.index\n\t\t})),\n\t\tmidpoints: a.midpoints.map((m) => ({ index: m.index, ...pt(m), hovered: hover?.kind === 'midpoint' && hover.index === m.index })),\n\t\textent: a.extent,\n\t\tremoving\n\t};\n}\n\n/** Blend groups the selection covers (a selected key counts as its blend). */\nfunction selectedBlends(state: Pick<VectorEditorState, 'document' | 'selection'>): VectorGroupNode[] {\n\tconst out = new Map<string, VectorGroupNode>();\n\tfor (const id of state.selection) {\n\t\tconst node = findVectorNode(state.document.nodes, id);\n\t\tconst group = isVectorBlendGroup(node) ? node : locateVectorNode(state.document.nodes, id)?.parent;\n\t\tif (isVectorBlendGroup(group)) out.set(group.id, group);\n\t}\n\treturn [...out.values()];\n}\n\nfunction blendOverlay(state: VectorEditorState): VectorOverlayRenderData['blend'] {\n\tconst v = state.viewport;\n\tconst view: AffineMatrix = [v.zoom, 0, 0, v.zoom, v.x, v.y];\n\tconst doc = state.document;\n\tconst sc = (p: Vec2, m: AffineMatrix) => {\n\t\tconst q = { x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] };\n\t\treturn { x: fmt(q.x), y: fmt(q.y) };\n\t};\n\tconst spines = selectedBlends(state).flatMap((g) => {\n\t\tconst s = vectorBlendSpine(g);\n\t\tif (!s) return [];\n\t\tconst m = multiplyMatrices(vectorWorldMatrix(doc.nodes, g.id) ?? IDENTITY_MATRIX, view);\n\t\tconst d = bezierPathToSvg(transformBezierPath(subpathsToBezier([s.spine], { corners: false }), m), { precision: 2 });\n\t\tconst keys = s.stops.map((stop) => {\n\t\t\tconst p = vectorBlendRailAt(s.rail, stop).point;\n\t\t\treturn sc({ x: p[0], y: p[1] }, m);\n\t\t});\n\t\treturn [{ id: g.id, d, keys, replaced: s.replaced }];\n\t});\n\tlet start: VectorOverlayRenderData['blend']['start'] = null;\n\tconst anchors: VectorOverlayRenderData['blend']['anchors'] = [];\n\tif (state.tool === 'blend') {\n\t\tconst from = state.blendStart ? findVectorNode(doc.nodes, state.blendStart.nodeId) : undefined;\n\t\tif (from && state.blendStart) {\n\t\t\tconst a = state.blendStart.anchor === null ? undefined : getAnchorWorld(doc, { nodeId: from.id, subpath: 0, index: state.blendStart.anchor });\n\t\t\tstart = { d: screenOutline(state, from, new Set()), anchor: a ? sc(a, view) : null };\n\t\t}\n\t\tconst hovered = state.hover?.nodeId ? findVectorNode(doc.nodes, state.hover.nodeId) : undefined;\n\t\tif (hovered?.type === 'path' && hovered.subpaths[0]) {\n\t\t\thovered.subpaths[0].anchors.forEach((_, index) => {\n\t\t\t\tconst a = getAnchorWorld(doc, { nodeId: hovered.id, subpath: 0, index });\n\t\t\t\tif (a) anchors.push({ ...sc(a, view), hovered: state.hover?.anchor?.index === index });\n\t\t\t});\n\t\t}\n\t}\n\treturn { spines, start, anchors };\n}\n\n/** What the Blend controls of a toolbar or style panel show. */\nexport interface VectorBlendControls {\n\t/** Ids of the selected blends (a selected key counts as its blend). */\n\tids: string[];\n\t/** The first selected blend's options, or the options new blends get. */\n\toptions: Required<VectorBlendOptions>;\n\t/** Two or more shapes that share a parent are selected: Blend can run. */\n\tcanBlend: boolean;\n\t/** One blend and one path are selected: Replace Spine can run. */\n\tcanReplaceSpine: boolean;\n\t/** The first selected blend follows a replaced spine. */\n\thasSpine: boolean;\n}\n\n/** State of the Blend controls for the current selection. */\nexport function getVectorBlendControls(state: Pick<VectorEditorState, 'document' | 'selection' | 'blendDefaults'>): VectorBlendControls {\n\tconst blends = selectedBlends(state);\n\tconst doc = state.document;\n\tconst roots = state.selection.filter((id) => {\n\t\tconst node = findVectorNode(doc.nodes, id);\n\t\treturn !!node && !node.blendStep && !node.locked && !state.selection.includes(locateVectorNode(doc.nodes, id)?.parent?.id ?? '');\n\t});\n\tconst parents = new Set(roots.map((id) => locateVectorNode(doc.nodes, id)?.parent?.id ?? null));\n\tconst paths = roots.filter((id) => findVectorNode(doc.nodes, id)?.type === 'path' && !isVectorBlendGroup(locateVectorNode(doc.nodes, id)?.parent));\n\tconst first = blends[0];\n\treturn {\n\t\tids: blends.map((g) => g.id),\n\t\toptions: resolveVectorBlendOptions(first ? first.blend : state.blendDefaults),\n\t\tcanBlend: roots.length >= 2 && parents.size === 1,\n\t\tcanReplaceSpine: blends.length === 1 && paths.length === 1,\n\t\thasSpine: !!first?.blend?.spine\n\t};\n}\n\n/**\n * Applies blend options from a toolbar: to the selected blends (one undo step) and to the\n * options new blends get, so the controls keep showing what was picked.\n */\nexport function applyVectorBlendOptions(controller: VectorEditorController, patch: Partial<VectorBlendOptions>): void {\n\tcontroller.setBlendDefaults(patch);\n\tif (getVectorBlendControls(controller.getState()).ids.length) controller.setBlendOptions(patch);\n}\n\n/** Labels of the blend spacing modes, for toolbars. */\nexport const VECTOR_BLEND_SPACING_LABELS: Readonly<Record<VectorBlendSpacing, string>> = { steps: 'Specified Steps', distance: 'Specified Distance', smoothColor: 'Smooth Color' };\n\n// ---------------------------------------------------------------------------\n// Event adapters (duck-typed, so they work with DOM, React and Svelte events)\n// ---------------------------------------------------------------------------\n\n/** Colours and sizes (screen pixels) the bundled canvases draw the overlay with. */\nexport const VECTOR_OVERLAY_THEME = {\n\taccent: '#0ea5e9',\n\t/** Smart guide lines and labels. */\n\tguide: '#ff3dfc',\n\thandleFill: '#ffffff',\n\tmarqueeFill: 'rgba(14, 165, 233, 0.08)',\n\t/** The eraser's swath while it is dragged. */\n\teraserFill: 'rgba(239, 68, 68, 0.25)',\n\ttransformHandleSize: 8,\n\tanchorSize: 7,\n\thandleRadius: 3.5,\n\t/** Radius of a live corner widget (a ring with a dot). */\n\tcornerWidgetRadius: 4,\n\t/** Half the diagonal of a width point's diamond. */\n\twidthPointSize: 5,\n\t/** Shape Builder faces to merge and paint bucket regions. */\n\tbuilderFill: 'rgba(14, 165, 233, 0.22)',\n\t/** Shape Builder faces and line pieces an Alt gesture deletes. */\n\tbuilderErase: '#ef4444',\n\tbuilderEraseFill: 'rgba(239, 68, 68, 0.22)',\n\t/** Opacity of the hatch lines over Shape Builder faces (drawn in the face's outline colour). */\n\tbuilderHatchOpacity: 0.6\n} as const;\n\n/** SVG path data of the diamond a width point is drawn with (screen pixels). */\nexport function vectorDiamondPath(x: number, y: number, r: number = VECTOR_OVERLAY_THEME.widthPointSize): string {\n\treturn `M${fmt(x)} ${fmt(y - r)}L${fmt(x + r)} ${fmt(y)}L${fmt(x)} ${fmt(y + r)}L${fmt(x - r)} ${fmt(y)}Z`;\n}\n\ninterface ModifierEventLike {\n\tshiftKey?: boolean;\n\taltKey?: boolean;\n\tmetaKey?: boolean;\n\tctrlKey?: boolean;\n}\n\n/** Modifiers of a mouse / pointer / key event (`mod` is Cmd or Ctrl). */\nexport function toVectorModifiers(event: ModifierEventLike): { shift: boolean; alt: boolean; mod: boolean } {\n\treturn { shift: !!event.shiftKey, alt: !!event.altKey, mod: !!(event.metaKey || event.ctrlKey) };\n}\n\n/**\n * A pointer event as `VectorEditorController.pointerDown/Move/Up` input: modifiers, button,\n * and the pressure and pointer type the pencil records as a width profile.\n */\nexport function toVectorPointerInput(event: ModifierEventLike & { button?: number; pressure?: number; pointerType?: string }) {\n\treturn {\n\t\t...toVectorModifiers(event),\n\t\tbutton: event.button,\n\t\t...(typeof event.pressure === 'number' ? { pressure: event.pressure } : {}),\n\t\t...(event.pointerType ? { pointerType: event.pointerType } : {})\n\t};\n}\n\n/** A key event as `VectorEditorController.keyDown/keyUp` input. */\nexport function toVectorKeyInput(event: ModifierEventLike & { key: string }) {\n\treturn { key: event.key, ...toVectorModifiers(event) };\n}\n\n/** True for targets that should keep their own keys (inputs, textareas, contenteditable). */\nexport function isVectorTextTarget(target: unknown): boolean {\n\tconst el = target as { tagName?: string; isContentEditable?: boolean; getAttribute?: (name: string) => string | null } | null;\n\tif (!el || typeof el !== 'object') return false;\n\tconst tag = (el.tagName ?? '').toUpperCase();\n\tif (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;\n\treturn !!el.isContentEditable || el.getAttribute?.('contenteditable') === 'true';\n}\n\n// ---------------------------------------------------------------------------\n// Style panel helpers\n// ---------------------------------------------------------------------------\n\n/** Style shown in a style panel: the first selected path's, with `mixed` flags when paths differ. */\nexport interface VectorSelectionStyle {\n\tfill: VectorPaint;\n\tstroke: VectorPaint;\n\tstrokeWidth: number;\n\topacity: number;\n\tstrokeAlign: VectorStrokeAlign;\n\tstartArrow: VectorArrowhead | null;\n\tendArrow: VectorArrowhead | null;\n\tarrowAlign: VectorArrowAlign;\n\tstartArrowScale: number;\n\tendArrowScale: number;\n\t/** The first path's width profile (null when uniform) and the preset it matches. */\n\tstrokeWidthProfile: VectorWidthPoint[] | null;\n\twidthProfile: VectorWidthPreset | 'custom';\n\t/**\n\t * Live corners of the selected anchors (direct tool) or of every corner of the selected\n\t * paths: the first one's radius and kind, and how many corners there are (0: none to round).\n\t */\n\tcornerRadius: number;\n\tcornerKind: VectorCornerKind;\n\tcorners: number;\n\t/** Some selected path is open, so arrowheads apply. */\n\thasOpenPath: boolean;\n\tmixed: {\n\t\tfill: boolean;\n\t\tstroke: boolean;\n\t\tstrokeWidth: boolean;\n\t\topacity: boolean;\n\t\tstrokeAlign: boolean;\n\t\tstartArrow: boolean;\n\t\tendArrow: boolean;\n\t\tarrowAlign: boolean;\n\t\tstartArrowScale: boolean;\n\t\tendArrowScale: boolean;\n\t\twidthProfile: boolean;\n\t\tcornerRadius: boolean;\n\t\tcornerKind: boolean;\n\t};\n\t/** Ids of the paths the style applies to. */\n\tpathIds: string[];\n}\n\nexport function getVectorSelectionStyle(\n\tstate: Pick<VectorEditorState, 'document' | 'selection'> & Partial<Pick<VectorEditorState, 'selectedAnchors' | 'tool'>>\n): VectorSelectionStyle | null {\n\tconst paths: VectorPathNode[] = [];\n\tfor (const id of state.selection) {\n\t\tconst node = findVectorNode(state.document.nodes, id);\n\t\tif (node) paths.push(...collect(node));\n\t}\n\tconst anchorMode = state.tool === 'direct' && !!state.selectedAnchors?.length;\n\tif (anchorMode) {\n\t\tfor (const r of state.selectedAnchors!) {\n\t\t\tif (paths.some((p) => p.id === r.nodeId)) continue;\n\t\t\tconst node = findVectorNode(state.document.nodes, r.nodeId);\n\t\t\tif (node?.type === 'path') paths.push(node);\n\t\t}\n\t}\n\tif (!paths.length) return null;\n\tconst first = paths[0];\n\tconst same = (get: (p: VectorPathNode) => unknown) => paths.every((p) => JSON.stringify(get(p) ?? null) === JSON.stringify(get(first) ?? null));\n\t// Corners: those of the selected anchors in the direct tool, else every corner of the paths.\n\tconst corners: { radius: number; kind: VectorCornerKind }[] = [];\n\tfor (const p of paths) {\n\t\tfor (const c of vectorCorners(p.subpaths)) {\n\t\t\tif (anchorMode && !state.selectedAnchors!.some((r) => r.nodeId === p.id && r.subpath === c.subpath && r.index === c.index)) continue;\n\t\t\tconst corner = p.subpaths[c.subpath].anchors[c.index].corner;\n\t\t\tcorners.push({ radius: corner?.radius ?? 0, kind: corner?.kind ?? 'round' });\n\t\t}\n\t}\n\tconst c0 = corners[0] ?? { radius: 0, kind: 'round' as const };\n\treturn {\n\t\tfill: first.fill ?? null,\n\t\tstroke: first.stroke ?? null,\n\t\tstrokeWidth: first.strokeWidth ?? 1,\n\t\topacity: first.opacity ?? 1,\n\t\tstrokeAlign: first.strokeAlign ?? 'center',\n\t\tstartArrow: first.startArrow ?? null,\n\t\tendArrow: first.endArrow ?? null,\n\t\tarrowAlign: first.arrowAlign ?? 'extend',\n\t\tstartArrowScale: first.startArrowScale ?? 1,\n\t\tendArrowScale: first.endArrowScale ?? 1,\n\t\tstrokeWidthProfile: profileOf(first),\n\t\twidthProfile: widthProfilePresetId(profileOf(first)),\n\t\tcornerRadius: c0.radius,\n\t\tcornerKind: c0.kind,\n\t\tcorners: corners.length,\n\t\thasOpenPath: paths.some((p) => p.subpaths.some((sp) => !sp.closed && sp.anchors.length > 1)),\n\t\tmixed: {\n\t\t\tfill: !same((p) => p.fill),\n\t\t\tstroke: !same((p) => p.stroke),\n\t\t\tstrokeWidth: !same((p) => p.strokeWidth ?? 1),\n\t\t\topacity: !same((p) => p.opacity ?? 1),\n\t\t\tstrokeAlign: !same((p) => p.strokeAlign ?? 'center'),\n\t\t\tstartArrow: !same((p) => p.startArrow ?? null),\n\t\t\tendArrow: !same((p) => p.endArrow ?? null),\n\t\t\tarrowAlign: !same((p) => p.arrowAlign ?? 'extend'),\n\t\t\tstartArrowScale: !same((p) => p.startArrowScale ?? 1),\n\t\t\tendArrowScale: !same((p) => p.endArrowScale ?? 1),\n\t\t\twidthProfile: !same(profileOf),\n\t\t\tcornerRadius: corners.some((c) => c.radius !== c0.radius),\n\t\t\tcornerKind: corners.some((c) => c.kind !== c0.kind)\n\t\t},\n\t\tpathIds: paths.map((p) => p.id)\n\t};\n}\n\nfunction profileOf(p: VectorPathNode): VectorWidthPoint[] | null {\n\tconst points = sanitizeWidthProfile(p.strokeWidthProfile);\n\treturn points.length && widthProfilePresetId(points) !== 'uniform' ? points : null;\n}\n\n/** Labels of the stroke alignments, corner kinds and arrowheads, for style panels. */\nexport const VECTOR_STROKE_ALIGN_LABELS: Readonly<Record<VectorStrokeAlign, string>> = { center: 'Center', inside: 'Inside', outside: 'Outside' };\nexport const VECTOR_CORNER_KIND_LABELS: Readonly<Record<VectorCornerKind, string>> = { round: 'Round', inverted: 'Inverted', chamfer: 'Chamfer' };\nexport const VECTOR_ARROWHEAD_LABELS: Readonly<Record<VectorArrowhead, string>> = {\n\ttriangle: 'Triangle',\n\tarrow: 'Arrow',\n\topen: 'Open arrow',\n\tcircle: 'Circle',\n\tsquare: 'Square',\n\tbar: 'Bar'\n};\n\n/** A paint as `#rrggbb` for `<input type=\"color\">`, or null for none, gradients and other CSS colours. */\nexport function vectorPaintToHex(paint: VectorPaint | undefined): string | null {\n\tif (typeof paint !== 'string') return null;\n\tconst m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(paint.trim());\n\tif (!m) return null;\n\tconst hex = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];\n\treturn `#${hex.toLowerCase()}`;\n}\n\n// ---------------------------------------------------------------------------\n// Gradient editor\n// ---------------------------------------------------------------------------\n\nexport const VECTOR_GRADIENT_TYPE_LABELS: Readonly<Record<VectorGradientType, string>> = { linear: 'Linear', radial: 'Radial', conic: 'Conic', reflected: 'Reflected', diamond: 'Diamond' };\nexport const VECTOR_GRADIENT_SPREAD_LABELS: Readonly<Record<VectorGradientSpread, string>> = { pad: 'Pad', reflect: 'Reflect', repeat: 'Repeat' };\nexport const VECTOR_GRADIENT_INTERPOLATION_LABELS: Readonly<Record<VectorGradientInterpolation, string>> = { srgb: 'sRGB', linear: 'Linear light', oklab: 'Oklab' };\nexport const VECTOR_STROKE_GRADIENT_MODE_LABELS: Readonly<Record<VectorStrokeGradientMode, string>> = { within: 'Within stroke', along: 'Along stroke', across: 'Across stroke' };\n\n/** What a gradient editor shows for the selection's `target` paint (see `getVectorGradientEditor`). */\nexport interface VectorGradientEditorData {\n\ttarget: 'fill' | 'stroke';\n\t/** The first selected path's paint is a gradient. */\n\tgradient: VectorGradient | null;\n\t/** The paint as a `#rrggbb` colour when it is one (what a new gradient starts from). */\n\tcolor: string | null;\n\t/** Sorted stops; `index` is what `setGradientStop` takes. */\n\tstops: (VectorGradientStop & { index: number; selected: boolean })[];\n\tselected: (VectorGradientStop & { index: number; last: boolean }) | null;\n\t/** A CSS `linear-gradient(to right, …)` of the ramp (midpoints and colour space expanded). */\n\tpreview: string | null;\n\ttype: VectorGradientType | null;\n\tspread: VectorGradientSpread;\n\tinterpolation: VectorGradientInterpolation;\n\tsmoothness: number;\n\tstrokeMode: VectorStrokeGradientMode;\n\t/** The selected paths' gradients differ. */\n\tmixed: boolean;\n}\n\n/** The gradient editor of the selection's fill or stroke (`state.gradientEdit.target`), or null with nothing selected. */\nexport function getVectorGradientEditor(state: Pick<VectorEditorState, 'document' | 'selection' | 'gradientEdit'>): VectorGradientEditorData | null {\n\tconst paths: VectorPathNode[] = [];\n\tfor (const id of state.selection) {\n\t\tconst node = findVectorNode(state.document.nodes, id);\n\t\tif (node) paths.push(...collect(node));\n\t}\n\tif (!paths.length) return null;\n\tconst target = state.gradientEdit.target;\n\tconst paint = paths[0][target];\n\tconst gradient = isVectorGradient(paint) ? paint : null;\n\tconst sorted = gradient ? sortedVectorGradientStops(gradient) : [];\n\tconst stopIndex = state.gradientEdit.stop;\n\tconst stops = sorted.map((s, index) => ({ ...s, index, selected: index === stopIndex }));\n\tconst sel = stopIndex !== null && sorted[stopIndex] ? { ...sorted[stopIndex], index: stopIndex, last: stopIndex === sorted.length - 1 } : null;\n\tconst preview = gradient\n\t\t? `linear-gradient(to right, ${expandVectorGradientStops(gradient)\n\t\t\t\t.map((s) => {\n\t\t\t\t\tconst c = parseVectorColor(s.color);\n\t\t\t\t\tconst a = (c?.[3] ?? 1) * (s.opacity ?? 1);\n\t\t\t\t\tconst color = c ? `rgba(${Math.round(c[0] * 255)}, ${Math.round(c[1] * 255)}, ${Math.round(c[2] * 255)}, ${Math.round(a * 1000) / 1000})` : s.color;\n\t\t\t\t\treturn `${color} ${Math.round(s.offset * 10000) / 100}%`;\n\t\t\t\t})\n\t\t\t\t.join(', ')})`\n\t\t: null;\n\treturn {\n\t\ttarget,\n\t\tgradient,\n\t\tcolor: vectorPaintToHex(paint),\n\t\tstops,\n\t\tselected: sel,\n\t\tpreview,\n\t\ttype: gradient?.type ?? null,\n\t\tspread: gradient?.spread ?? 'pad',\n\t\tinterpolation: gradient?.interpolation ?? 'srgb',\n\t\tsmoothness: gradient?.smoothness ?? 0,\n\t\tstrokeMode: gradient?.strokeMode ?? 'within',\n\t\tmixed: paths.some((p) => JSON.stringify(p[target] ?? null) !== JSON.stringify(paint ?? null))\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Toolbar helpers\n// ---------------------------------------------------------------------------\n\n/** A path-editing button of the bundled toolbars. */\nexport interface VectorPathAction {\n\tid: 'join' | 'average' | 'cut' | 'clean' | 'smooth' | 'outline';\n\tlabel: string;\n\ttitle: string;\n\t/** Shortcut after Cmd / Ctrl, if any. */\n\tshortcut?: string;\n\t/** Whether it can run with `selected` nodes and `points` selected anchors (direct tool). */\n\tenabled: (selected: number, points: number) => boolean;\n\trun: (controller: VectorEditorController) => unknown;\n}\n\n/** Join, Average, Cut at Points, Remove Redundant Points, Smooth and Outline Stroke, as the toolbars show them. */\nexport const VECTOR_PATH_ACTIONS: readonly VectorPathAction[] = [\n\t{ id: 'join', label: '⋈', title: 'Join', shortcut: 'J', enabled: (s, p) => s + p > 0, run: (c) => c.joinPaths() },\n\t{ id: 'average', label: '⊹', title: 'Average Points', shortcut: 'Alt+J', enabled: (_s, p) => p > 1, run: (c) => c.averageAnchors('both') },\n\t{ id: 'cut', label: '✂·', title: 'Cut at Points', enabled: (_s, p) => p > 0, run: (c) => c.cutAtAnchors() },\n\t{ id: 'clean', label: '⌁', title: 'Remove Redundant Points', enabled: (s, p) => s + p > 0, run: (c) => c.removeRedundantPoints() },\n\t{ id: 'smooth', label: '∿', title: 'Smooth', enabled: (s, p) => s + p > 0, run: (c) => c.smoothPaths() },\n\t{ id: 'outline', label: '⊡', title: 'Outline Stroke', enabled: (s) => s > 0, run: (c) => c.outlineStroke() }\n];\n\n/** A Pathfinder button of the bundled toolbars. */\nexport interface VectorPathfinderAction {\n\tid: VectorPathfinderOperation;\n\tlabel: string;\n\ttitle: string;\n\t/** Paths (or groups) it needs selected. */\n\tmin: number;\n}\n\n/** Divide, Trim, Merge, Crop, Outline and Minus Back, as the toolbars show them. */\nexport const VECTOR_PATHFINDER_ACTIONS: readonly VectorPathfinderAction[] = [\n\t{ id: 'divide', label: '⊞', title: 'Divide', min: 1 },\n\t{ id: 'trim', label: '⧉', title: 'Trim', min: 2 },\n\t{ id: 'merge', label: '⧈', title: 'Merge', min: 2 },\n\t{ id: 'crop', label: '⌗', title: 'Crop', min: 2 },\n\t{ id: 'outline', label: '⬚', title: 'Outline', min: 1 },\n\t{ id: 'minusBack', label: '⊖', title: 'Minus Back', min: 2 }\n];\n\n/** The on / off path-editing settings the toolbars toggle. */\nexport const VECTOR_EDIT_TOGGLES: readonly { key: 'keepShapeOnDelete' | 'curvatureMoves' | 'recognizeShapes'; label: string; title: string }[] = [\n\t{ key: 'keepShapeOnDelete', label: 'Keep', title: 'Keep shape when deleting points' },\n\t{ key: 'curvatureMoves', label: 'Curv', title: 'Curvature point moves (Alt flips while dragging)' },\n\t{ key: 'recognizeShapes', label: 'Shapes', title: 'Turn pencil scribbles into shapes' }\n];\n\n\n// ---------------------------------------------------------------------------\n// Effects panel\n// ---------------------------------------------------------------------------\n\n/** One effect of the listed stack, with what a panel needs to edit it. */\nexport interface VectorEffectsPanelItem {\n\tid: string;\n\ttype: VectorEffectType;\n\tlabel: string;\n\tenabled: boolean;\n\tparams: Record<string, unknown>;\n\tspecs: readonly VectorEffectParamSpec[];\n}\n\n/** What an Effects panel shows for the selection. */\nexport interface VectorEffectsPanelData {\n\t/** The node whose stack is listed (when one node is selected), else null. */\n\tnodeId: string | null;\n\t/** Selected nodes an added effect goes to. */\n\tcount: number;\n\t/** The listed stack, applied top to bottom. */\n\teffects: VectorEffectsPanelItem[];\n\t/** Something selected is live (Expand Appearance applies). */\n\texpandable: boolean;\n}\n\n/** Effect types in menu order, with their names. */\nexport const VECTOR_EFFECT_MENU: readonly { type: VectorEffectType; label: string }[] = VECTOR_EFFECT_TYPES.map((type) => ({ type, label: VECTOR_EFFECT_LABELS[type] }));\n\n/** The Effects panel of the selection, or null when nothing that takes effects is selected. */\nexport function getVectorEffectsPanel(state: Pick<VectorEditorState, 'document' | 'selection'>): VectorEffectsPanelData | null {\n\tconst nodes = state.selection.map((id) => findVectorNode(state.document.nodes, id)).filter((n): n is VectorNode => !!n && !n.blendStep);\n\tif (!nodes.length) return null;\n\tconst one = nodes.length === 1 ? nodes[0] : null;\n\treturn {\n\t\tnodeId: one?.id ?? null,\n\t\tcount: nodes.length,\n\t\teffects: (one?.effects ?? []).map((e) => ({\n\t\t\tid: e.id,\n\t\t\ttype: e.type,\n\t\t\tlabel: VECTOR_EFFECT_LABELS[e.type] ?? e.type,\n\t\t\tenabled: e.enabled !== false,\n\t\t\tparams: e.params as unknown as Record<string, unknown>,\n\t\t\tspecs: VECTOR_EFFECT_PARAM_SPECS[e.type] ?? []\n\t\t})),\n\t\texpandable: nodes.some(hasVectorAppearance)\n\t};\n}\n","import type { CommandSpec } from '../commands/registry.js';\nimport type { VectorEditorController, VectorShapeInput } from './controller.js';\nimport { vectorNodeBounds, vectorNodeRotation, vectorParentMatrix, vectorPathData, IDENTITY_MATRIX } from './geometry.js';\nimport type { VectorAlign, VectorAnchorRef, VectorArrange, VectorAverageAxis, VectorBooleanOperation, VectorCornerKind, VectorNode, VectorPathfinderOperation, VectorStyle, VectorTool } from './types.js';\nimport { VECTOR_ARROWHEADS, VECTOR_CORNER_KINDS, VECTOR_EFFECT_TYPES, VECTOR_PATHFINDER_OPERATIONS, VECTOR_TOOLS, type VectorEffect, type VectorEffectType, type VectorWidthPoint } from './types.js';\nimport { VECTOR_WIDTH_PRESET_IDS, type VectorTaperOptions, type VectorWaveOptions, type VectorWidthPreset } from './width.js';\nimport { resolveVectorBlendOptions, vectorBlendKeys } from './blendGroup.js';\nimport { VECTOR_BLEND_SPACINGS, VECTOR_GRADIENT_INTERPOLATIONS, VECTOR_GRADIENT_SPREADS, VECTOR_GRADIENT_TYPES, VECTOR_STROKE_GRADIENT_MODES, type VectorBlendOptions } from './types.js';\nimport { sanitizeVectorGradient, type VectorGradientPatch } from './gradient.js';\n\nexport interface VectorCommandOptions {\n\t/** Id prefix (default `vector.`); use one per editor when several are registered. */\n\tprefix?: string;\n\t/** Palette group (default `Vector`). */\n\tgroup?: string;\n\t/** Command scope. Omit for global commands. */\n\tscope?: string;\n}\n\n/** A node as listed for agents. */\nexport interface VectorNodeListing {\n\tid: string;\n\ttype: 'path' | 'group';\n\tname?: string;\n\tdepth: number;\n\t/** Axis-aligned bounds in document units. */\n\tbounds: { x: number; y: number; width: number; height: number } | null;\n\t/** Rotation in degrees, clockwise (ancestors' rotations included). */\n\trotation: number;\n\tvisible: boolean;\n\tlocked: boolean;\n\tselected: boolean;\n\tfill?: unknown;\n\tstroke?: unknown;\n\tstrokeWidth?: number;\n\t/** SVG path data (paths only). */\n\td?: string;\n\tchildren?: number;\n\t/** A blend group's options (its steps are listed as children with `blendStep`). */\n\tblend?: { spacing: string; steps: number; distance: number; orientToSpine: boolean; linear: boolean; spine: boolean; keys: string[] };\n\t/** An in-between generated by a blend (locked; changes come from its keys). */\n\tblendStep?: boolean;\n\t/** The node's live path effects, applied in order. */\n\teffects?: VectorEffect[];\n\t/** A live boolean group's operation (its children are the operands). */\n\tliveBoolean?: VectorBooleanOperation;\n\t/** An operand's own operation in a live boolean group. */\n\tbooleanOperation?: VectorBooleanOperation;\n}\n\nconst EFFECT_PARAMS_DESCRIPTION =\n\t'Effect parameters (missing ones keep their value or default). trim: start, end (percent), offset (degrees), mode simultaneous|individual. roundCorners: radius. puckerBloat: amount (−100…100). twist: angle, center {x, y} (offset from the middle). zigzag: size, ridges, smooth. wiggle: size, detail, smooth, speed, correlation, phase, seed, time (seconds). offset: amount, join miter|round|bevel, miterLimit, copies, copyOffset. merge: mode merge|add|subtract|intersect|exclude. repeater: copies (fractions fade the last), offset, position {x, y}, anchor {x, y} (offset from the middle), rotation, scale {x, y} (percent), startOpacity, endOpacity (percent), composite below|above.';\n\nconst fail = (message: string): never => {\n\tthrow new Error(message);\n};\n\nconst IDS = { type: 'array', items: { type: 'string' }, description: 'Node ids (default: the selection).' } as const;\n\nconst ANCHORS = {\n\ttype: 'array',\n\titems: {\n\t\ttype: 'object',\n\t\tproperties: { nodeId: { type: 'string' }, subpath: { type: 'integer', minimum: 0 }, index: { type: 'integer', minimum: 0 } },\n\t\trequired: ['nodeId', 'subpath', 'index']\n\t},\n\tdescription: 'Anchors as { nodeId, subpath, index } (default: the selected points).'\n} as const;\n\nconst POINTS = {\n\ttype: 'array',\n\titems: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2 },\n\tminItems: 1,\n\tdescription: 'Polyline [[x, y], …] in document units.'\n} as const;\n\nconst WIDTH_POINTS = {\n\ttype: ['array', 'null'],\n\titems: {\n\t\ttype: 'object',\n\t\tproperties: {\n\t\t\tt: { type: 'number', minimum: 0, maximum: 1, description: 'Position along each subpath by length (0 start, 1 end).' },\n\t\t\tleft: { type: 'number', minimum: 0, description: 'Left side as a multiple of half the stroke width (1 = uniform).' },\n\t\t\tright: { type: 'number', minimum: 0, description: 'Right side (looking along the path, y down).' }\n\t\t},\n\t\trequired: ['t', 'left', 'right']\n\t},\n\tdescription: 'Variable-width profile points, interpolated smoothly (monotone cubic); two points at one `t` make a step. `null` or `[]` strokes at a uniform width.'\n} as const;\n\nconst BLEND_PROPS = {\n\tspacing: { type: 'string', enum: ['steps', 'distance', 'smoothColor'], description: 'How in-betweens are spaced (default steps).' },\n\tsteps: { type: 'integer', minimum: 1, maximum: 1000, description: 'In-betweens between each pair of keys (steps spacing; default 5).' },\n\tdistance: { type: 'number', exclusiveMinimum: 0, description: 'Distance between in-betweens along the spine (distance spacing; default 20).' },\n\torientToSpine: { type: 'boolean', description: 'Turn the in-betweens with a replaced spine.' },\n\tlinear: { type: 'boolean', description: 'Move anchors in straight lines instead of the rotation-aware morph.' }\n} as const;\n\nconst checkBlendOptions = (params: Partial<VectorBlendOptions> | undefined) => {\n\tif (params?.spacing !== undefined && !VECTOR_BLEND_SPACINGS.includes(params.spacing)) fail(`Unknown blend spacing \"${params.spacing}\".`);\n\tif (params?.steps !== undefined && !(Number.isFinite(params.steps) && params.steps >= 1)) fail('steps must be at least 1.');\n\tif (params?.distance !== undefined && !(Number.isFinite(params.distance) && params.distance > 0)) fail('distance must be positive.');\n};\n\nconst POINT = { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, required: ['x', 'y'] } as const;\n\n/** Gradient fields shared by paints and `setGradient`. */\nconst GRADIENT_PROPS = {\n\ttype: { type: 'string', enum: VECTOR_GRADIENT_TYPES, description: 'linear, radial, conic (an angle sweep), reflected (linear mirrored about the start) or diamond.' },\n\tstops: {\n\t\ttype: 'array',\n\t\tminItems: 1,\n\t\titems: {\n\t\t\ttype: 'object',\n\t\t\tproperties: {\n\t\t\t\toffset: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\tcolor: { type: 'string', description: 'A CSS colour.' },\n\t\t\t\topacity: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\tmidpoint: { type: 'number', minimum: 0.05, maximum: 0.95, description: 'Where between this stop and the next the colours are half-mixed (default 0.5).' }\n\t\t\t},\n\t\t\trequired: ['offset', 'color']\n\t\t}\n\t},\n\tangle: { type: 'number', description: 'Shorthand direction in degrees (0 up, clockwise): linear / reflected direction across the box, conic start angle, diamond turn.' },\n\tcx: { type: 'number', description: 'Shorthand centre x of radial / conic / diamond gradients, a fraction of the box (default 0.5).' },\n\tcy: { type: 'number' },\n\tr: { type: 'number', description: 'Shorthand radius, a fraction of the box (default 0.5).' },\n\tstart: { ...POINT, description: 'Explicit start (centre of radial / conic / diamond), in `units`. Wins over the shorthand.' },\n\tend: { ...POINT, description: 'Explicit end of the main axis (the radius of radial / conic / diamond).' },\n\twidth: { ...POINT, description: 'End of the secondary axis (aspect and turn of radial / conic / diamond); default square to the main axis, as long.' },\n\tfocal: { ...POINT, description: 'Radial focal point (where t = 0), in `units`.' },\n\tfocalRadius: { type: 'number', minimum: 0, maximum: 0.99, description: 'Radial focal circle radius as a fraction of the radius (SVG fr).' },\n\tunits: { type: 'string', enum: ['objectBoundingBox', 'userSpaceOnUse'], description: 'Space of start / end / width / focal: fractions of the shape box (default) or the node\\'s own coordinates.' },\n\tspread: { type: 'string', enum: VECTOR_GRADIENT_SPREADS, description: 'What paints past the ends: pad (default), reflect or repeat.' },\n\tinterpolation: { type: 'string', enum: VECTOR_GRADIENT_INTERPOLATIONS, description: 'Colour mixing space: srgb (default), linear (light) or oklab.' },\n\tsmoothness: { type: 'number', minimum: 0, maximum: 1, description: 'Bends the ramp along a smooth spline through the stops (0 = straight).' },\n\tstrokeMode: { type: 'string', enum: VECTOR_STROKE_GRADIENT_MODES, description: 'On strokes: within (default, like a fill), along the path or across it.' }\n} as const;\n\nconst PAINT = {\n\tdescription:\n\t\t'A CSS colour, `null` for none, or a gradient object: `{ type: \"linear\" | \"radial\" | \"conic\" | \"reflected\" | \"diamond\", stops: [{ offset, color, opacity?, midpoint? }] }` plus the shorthand `angle` / `cx, cy, r` or explicit `start`, `end`, `width`, `focal`, `focalRadius`, `units`, and `spread`, `interpolation`, `smoothness`, `strokeMode` (see vector.setGradient).',\n\tanyOf: [{ type: 'string' }, { type: 'null' }, { type: 'object', properties: GRADIENT_PROPS, required: ['type', 'stops'] }]\n} as const;\n\nconst STYLE_PROPS = {\n\tfill: PAINT,\n\tstroke: PAINT,\n\tstrokeWidth: { type: 'number', minimum: 0 },\n\tstrokeLinecap: { type: 'string', enum: ['butt', 'round', 'square'] },\n\tstrokeLinejoin: { type: 'string', enum: ['miter', 'round', 'bevel'] },\n\tstrokeDasharray: { type: 'array', items: { type: 'number' } },\n\tfillRule: { type: 'string', enum: ['nonzero', 'evenodd'] },\n\tstrokeAlign: { type: 'string', enum: ['center', 'inside', 'outside'], description: 'Where the stroke sits on the outline (closed shapes only; open paths stroke centred).' },\n\tstartArrow: { type: ['string', 'null'], enum: [...VECTOR_ARROWHEADS, null], description: 'Head at the start of an open path, or null for none.' },\n\tendArrow: { type: ['string', 'null'], enum: [...VECTOR_ARROWHEADS, null], description: 'Head at the end of an open path, or null for none.' },\n\tarrowAlign: { type: 'string', enum: ['extend', 'tip'], description: '`extend`: the tip sits past the end point; `tip`: on it, the stroke shortened under the head.' },\n\tstartArrowScale: { type: 'number', exclusiveMinimum: 0, description: 'Start head size as a multiple of the stroke width (default 1).' },\n\tendArrowScale: { type: 'number', exclusiveMinimum: 0, description: 'End head size as a multiple of the stroke width (default 1).' },\n\tstrokeWidthProfile: WIDTH_POINTS\n} as const;\n\nconst pickStyle = (params: Record<string, unknown>): VectorStyle => {\n\tconst out: Record<string, unknown> = {};\n\tfor (const key of Object.keys(STYLE_PROPS)) if (params[key] !== undefined) out[key] = params[key];\n\tfor (const key of ['fill', 'stroke'] as const) {\n\t\tconst paint = out[key];\n\t\tif (paint && typeof paint === 'object') out[key] = sanitizeVectorGradient(paint) ?? fail(`${key} is not a valid gradient: give a type and at least one stop { offset, color }.`);\n\t}\n\treturn out as VectorStyle;\n};\n\nconst check = (result: { ok: boolean; message?: string }) => {\n\tif (!result.ok) fail(result.message ?? 'The command failed.');\n};\n\n/**\n * Palette and agent commands for a vector editor: list, select, add shapes and paths, style,\n * transform, align, arrange, group, boolean operations and SVG import / export. Register them\n * in a `CommandRegistry` and agents get them through `createCommandToolProvider`. Failures\n * throw sentences, which the registry returns as `{ ok: false, code: 'failed', message }`.\n */\nexport function createVectorCommands(controller: VectorEditorController, options: VectorCommandOptions = {}): CommandSpec[] {\n\tconst prefix = options.prefix ?? 'vector.';\n\tconst group = options.group ?? 'Vector';\n\tconst base = { group, ...(options.scope ? { scope: options.scope } : {}) };\n\tconst hasSelection = () => (controller.getState().selection.length ? true : 'Nothing is selected.');\n\tconst ids = (params: { ids?: string[] } | undefined) => {\n\t\tconst list = params?.ids ?? controller.getState().selection;\n\t\tfor (const id of list) if (!controller.getNode(id)) fail(`There is no node \"${id}\".`);\n\t\tif (!list.length) fail('Nothing is selected; pass ids.');\n\t\treturn list;\n\t};\n\tconst enabledFor = (_: unknown, params: { ids?: string[] } | undefined) => (params?.ids?.length ? true : hasSelection());\n\tconst anchorsOf = (refs: VectorAnchorRef[] | undefined): VectorAnchorRef[] => {\n\t\tif (!refs) return [...controller.getState().selectedAnchors];\n\t\tfor (const r of refs) {\n\t\t\tconst node = controller.getNode(r.nodeId);\n\t\t\tif (node?.type !== 'path' || !node.subpaths[r.subpath]?.anchors[r.index]) fail(`There is no anchor ${r.index} on subpath ${r.subpath} of \"${r.nodeId}\".`);\n\t\t}\n\t\treturn refs;\n\t};\n\n\tconst list = (): { width: number; height: number; nodes: VectorNodeListing[]; selection: readonly string[]; tool: VectorTool } => {\n\t\tconst state = controller.getState();\n\t\tconst out: VectorNodeListing[] = [];\n\t\tconst visit = (nodes: readonly VectorNode[], depth: number) => {\n\t\t\tfor (const n of nodes) {\n\t\t\t\tconst parent = vectorParentMatrix(state.document.nodes, n.id) ?? IDENTITY_MATRIX;\n\t\t\t\tconst b = vectorNodeBounds(n, parent);\n\t\t\t\tout.push({\n\t\t\t\t\tid: n.id,\n\t\t\t\t\ttype: n.type,\n\t\t\t\t\t...(n.name ? { name: n.name } : {}),\n\t\t\t\t\tdepth,\n\t\t\t\t\trotation: Math.round(vectorNodeRotation(n, parent) * 100) / 100,\n\t\t\t\t\tbounds: b && { x: Math.round(b.x * 100) / 100, y: Math.round(b.y * 100) / 100, width: Math.round(b.width * 100) / 100, height: Math.round(b.height * 100) / 100 },\n\t\t\t\t\tvisible: n.visible !== false,\n\t\t\t\t\tlocked: !!n.locked,\n\t\t\t\t\tselected: state.selection.includes(n.id),\n\t\t\t\t\t...(n.type === 'path'\n\t\t\t\t\t\t? { fill: n.fill ?? null, stroke: n.stroke ?? null, strokeWidth: n.strokeWidth ?? 1, d: vectorPathData(n, 2) }\n\t\t\t\t\t\t: { children: n.children.length }),\n\t\t\t\t\t...(n.type === 'group' && n.blend\n\t\t\t\t\t\t? { blend: { ...resolveVectorBlendOptions(n.blend), spine: !!n.blend.spine, keys: vectorBlendKeys(n).map((k) => k.id) } }\n\t\t\t\t\t\t: {}),\n\t\t\t\t\t...(n.blendStep ? { blendStep: true } : {}),\n\t\t\t\t\t...(n.effects?.length ? { effects: n.effects } : {}),\n\t\t\t\t\t...(n.type === 'group' && n.liveBoolean ? { liveBoolean: n.liveBoolean } : {}),\n\t\t\t\t\t...(n.booleanOperation ? { booleanOperation: n.booleanOperation } : {})\n\t\t\t\t});\n\t\t\t\tif (n.type === 'group') visit(n.children, depth + 1);\n\t\t\t}\n\t\t};\n\t\tvisit(state.document.nodes, 0);\n\t\treturn { width: state.document.width, height: state.document.height, nodes: out, selection: state.selection, tool: state.tool };\n\t};\n\n\treturn [\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}list`,\n\t\t\tlabel: 'List Vector Nodes',\n\t\t\treadOnly: true,\n\t\t\tkeywords: ['layers', 'shapes', 'document', 'outline'],\n\t\t\tdescription: 'Lists every node (paths and groups, depth first) with bounds, style, path data and selection.',\n\t\t\trun: list\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}select`,\n\t\t\tlabel: 'Select Nodes',\n\t\t\tkeywords: ['pick', 'choose'],\n\t\t\tdescription: 'Selects nodes by id (empty list clears the selection).',\n\t\t\tparams: { type: 'object', properties: { ids: { type: 'array', items: { type: 'string' } }, add: { type: 'boolean' } }, required: ['ids'] },\n\t\t\trun: (_ctx: unknown, params: { ids: string[]; add?: boolean }) => {\n\t\t\t\tfor (const id of params.ids) if (!controller.getNode(id)) fail(`There is no node \"${id}\".`);\n\t\t\t\tif (!params.ids.length && !params.add) controller.clearSelection();\n\t\t\t\telse controller.select(params.ids, { add: params.add });\n\t\t\t\treturn { selection: controller.getState().selection };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}addShape`,\n\t\t\tlabel: 'Add Shape…',\n\t\t\tkeywords: ['rectangle', 'ellipse', 'circle', 'polygon', 'star', 'line', 'draw'],\n\t\t\tdescription: 'Adds a rectangle, ellipse, polygon, star or line in document units, using the current default style unless overridden.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tkind: { type: 'string', enum: ['rect', 'ellipse', 'polygon', 'star', 'line'] },\n\t\t\t\t\tx: { type: 'number' },\n\t\t\t\t\ty: { type: 'number' },\n\t\t\t\t\twidth: { type: 'number', description: 'For a line: the x offset of its end.' },\n\t\t\t\t\theight: { type: 'number', description: 'For a line: the y offset of its end.' },\n\t\t\t\t\tcornerRadius: { type: 'number', minimum: 0, description: 'Live corner radius of a rect, polygon or star.' },\n\t\t\t\t\tcornerKind: { type: 'string', enum: VECTOR_CORNER_KINDS },\n\t\t\t\t\tsides: { type: 'integer', minimum: 3 },\n\t\t\t\t\tpoints: { type: 'integer', minimum: 3 },\n\t\t\t\t\tinnerRatio: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\tname: { type: 'string' },\n\t\t\t\t\t...STYLE_PROPS\n\t\t\t\t},\n\t\t\t\trequired: ['kind', 'x', 'y', 'width', 'height']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: VectorShapeInput) => ({ id: controller.addShape(params) })\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}addPath`,\n\t\t\tlabel: 'Add Path…',\n\t\t\tkeywords: ['svg', 'path data', 'bezier', 'curve'],\n\t\t\tdescription: 'Adds a path from SVG path data (`d`), in document units.',\n\t\t\tparams: { type: 'object', properties: { d: { type: 'string' }, name: { type: 'string' }, ...STYLE_PROPS }, required: ['d'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { d: string; name?: string } & Record<string, unknown>) => {\n\t\t\t\tconst id = controller.addPath(params.d, { ...pickStyle(params), ...(params.name ? { name: params.name } : {}) });\n\t\t\t\tconst node = controller.getNode(id);\n\t\t\t\tif (node?.type === 'path' && !node.subpaths.length) {\n\t\t\t\t\tcontroller.undo();\n\t\t\t\t\tfail('The path data has no points.');\n\t\t\t\t}\n\t\t\t\treturn { id };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setStyle`,\n\t\t\tlabel: 'Set Style…',\n\t\t\tkeywords: ['fill', 'stroke', 'colour', 'color', 'gradient', 'width'],\n\t\t\tdescription: 'Sets fill, stroke and stroke settings of nodes (groups apply to every path inside).',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, ...STYLE_PROPS } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } & Record<string, unknown>) => {\n\t\t\t\tconst style = pickStyle(params);\n\t\t\t\tif (!Object.keys(style).length) fail('Pass at least one style property.');\n\t\t\t\tcheck(controller.setStyle(style, ids(params)));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setGradient`,\n\t\t\tlabel: 'Gradient…',\n\t\t\tkeywords: ['gradient', 'linear', 'radial', 'conic', 'angle', 'reflected', 'diamond', 'stops', 'midpoint', 'oklab', 'spread', 'focal', 'along stroke', 'across stroke', 'reverse'],\n\t\t\tdescription:\n\t\t\t\t'Edits the gradient of the fill (default) or stroke of paths (groups apply to every path inside): any gradient field (missing ones keep their value; a colour becomes a gradient from it to white), `aspect` (secondary axis over main axis), `reverse` (flip the stops and midpoints), `focal` / `width` null to remove them. Midpoints, smoothness and oklab / linear interpolation export to SVG as extra stops; conic and diamond gradients as patterns.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, target: { type: 'string', enum: ['fill', 'stroke'] }, ...GRADIENT_PROPS, aspect: { type: 'number' }, reverse: { type: 'boolean' } } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: ({ ids?: string[]; target?: 'fill' | 'stroke' } & VectorGradientPatch) | undefined) => {\n\t\t\t\tconst { ids: _i, target, ...patch } = params ?? {};\n\t\t\t\tif (patch.type !== undefined && !VECTOR_GRADIENT_TYPES.includes(patch.type)) fail(`Unknown gradient type \"${patch.type}\".`);\n\t\t\t\tif (patch.stops !== undefined && !(Array.isArray(patch.stops) && patch.stops.length)) fail('stops needs at least one { offset, color }.');\n\t\t\t\tcheck(controller.setGradient(patch, { ids: ids(params), target: target ?? 'fill' }));\n\t\t\t\tconst id = ids(params)[0];\n\t\t\t\tconst node = id ? controller.getNode(id) : undefined;\n\t\t\t\tconst path = node?.type === 'path' ? node : undefined;\n\t\t\t\treturn { ok: true, ...(path ? { gradient: path[target ?? 'fill'] } : {}) };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setCorners`,\n\t\t\tlabel: 'Corner Radius…',\n\t\t\tkeywords: ['round', 'rounded', 'corners', 'radius', 'fillet', 'chamfer', 'live corners'],\n\t\t\tdescription:\n\t\t\t\t'Sets the live corner radius (and optionally `kind`: round, inverted or chamfer) of `anchors`, else the selected points (direct tool), else every corner of `ids` / the selection. Radius 0 makes corners sharp; radii shrink to fit short sides. The anchors stay editable.',\n\t\t\tparams: { type: 'object', properties: { radius: { type: 'number', minimum: 0 }, kind: { type: 'string', enum: VECTOR_CORNER_KINDS }, ids: IDS, anchors: ANCHORS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { radius?: number; kind?: VectorCornerKind; ids?: string[]; anchors?: VectorAnchorRef[] } | undefined) => {\n\t\t\t\tconst refs = params?.anchors ? anchorsOf(params.anchors) : undefined;\n\t\t\t\tconst target = { refs, ids: params?.ids ? ids(params) : undefined };\n\t\t\t\tif (params?.radius !== undefined) check(controller.setCornerRadius(params.radius, { ...target, kind: params.kind }));\n\t\t\t\telse if (params?.kind) check(controller.setCornerKind(params.kind, target));\n\t\t\t\telse fail('Pass `radius` and / or `kind`.');\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setWidthProfile`,\n\t\t\tlabel: 'Width Profile…',\n\t\t\tkeywords: ['variable width', 'taper', 'tapered', 'pressure', 'profile', 'brush', 'calligraphy', 'bulge', 'thick', 'thin'],\n\t\t\tdescription:\n\t\t\t\t'Gives the stroke of paths a variable width: a `preset` (`uniform` removes the profile), explicit `points` ({ t, left, right }: side scales along each subpath), or a parametric `taper` (startLength / endLength as fractions of the path, startWidth / endWidth 0–1, startEase / endEase 0–1) and / or `wave` (amount 0–1, cycles, phase in degrees). Profiled strokes draw as filled outlines; Outline Stroke bakes them.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tids: IDS,\n\t\t\t\t\tpreset: { type: 'string', enum: VECTOR_WIDTH_PRESET_IDS },\n\t\t\t\t\tpoints: WIDTH_POINTS,\n\t\t\t\t\ttaper: {\n\t\t\t\t\t\ttype: 'object',\n\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\tstartLength: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\t\t\tendLength: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\t\t\tstartWidth: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\t\t\tendWidth: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\t\t\tstartEase: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\t\t\tendEase: { type: 'number', minimum: 0, maximum: 1 }\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\twave: { type: 'object', properties: { amount: { type: 'number', minimum: 0, maximum: 1 }, cycles: { type: 'number', exclusiveMinimum: 0 }, phase: { type: 'number' } } }\n\t\t\t\t}\n\t\t\t},\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (\n\t\t\t\t_ctx: unknown,\n\t\t\t\tparams: { ids?: string[]; preset?: VectorWidthPreset; points?: VectorWidthPoint[] | null; taper?: VectorTaperOptions; wave?: VectorWaveOptions } | undefined\n\t\t\t) => {\n\t\t\t\tconst target = { ids: ids(params) };\n\t\t\t\tif (params?.preset) check(controller.setWidthProfile(params.preset, target));\n\t\t\t\telse if (params?.points !== undefined) check(controller.setWidthProfile(params.points ?? null, target));\n\t\t\t\telse if (params?.taper || params?.wave) check(controller.setWidthProfile({ taper: params.taper, wave: params.wave }, target));\n\t\t\t\telse fail('Pass `preset`, `points`, `taper` or `wave`.');\n\t\t\t\tconst node = controller.getNode(target.ids[0]);\n\t\t\t\treturn { ok: true, points: node?.type === 'path' ? (node.strokeWidthProfile ?? []) : [] };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setWidthPoint`,\n\t\t\tlabel: 'Width Point…',\n\t\t\tkeywords: ['width point', 'variable width', 'stroke width at', 'taper', 'profile'],\n\t\t\tdescription:\n\t\t\t\t'Sets one width point of a path\\'s stroke profile numerically: `index` names an existing point (see `vector.setWidthProfile`\\'s result), leave it out to add one at `t`. `left` / `right` are side widths in document units (how far the stroke reaches on each side), `t` the position along each subpath (0–1); a point moved onto another\\'s `t` makes a step. Returns the profile.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tid: { type: 'string', description: 'The path.' },\n\t\t\t\t\tindex: { type: 'integer', minimum: 0 },\n\t\t\t\t\tt: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\tleft: { type: 'number', minimum: 0 },\n\t\t\t\t\tright: { type: 'number', minimum: 0 }\n\t\t\t\t},\n\t\t\t\trequired: ['id']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { id: string; index?: number; t?: number; left?: number; right?: number } | undefined) => {\n\t\t\t\tif (!params?.id) fail('Pass the path `id`.');\n\t\t\t\tcheck(controller.setWidthPoint({ nodeId: params!.id, index: params!.index ?? null }, { t: params!.t, left: params!.left, right: params!.right }));\n\t\t\t\tconst node = controller.getNode(params!.id);\n\t\t\t\treturn { ok: true, points: node?.type === 'path' ? (node.strokeWidthProfile ?? []) : [] };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}outlineStroke`,\n\t\t\tlabel: 'Outline Stroke',\n\t\t\tkeywords: ['expand', 'stroke to path', 'outline', 'convert stroke'],\n\t\t\tdescription: 'Turns the stroke of paths into filled shapes of the area it paints (alignment, caps, joins, dashes and arrowheads included). Returns the new ids.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tconst created = controller.outlineStroke({ ids: params?.ids ? ids(params) : undefined });\n\t\t\t\tif (!created.length) fail('No selected path has a stroke.');\n\t\t\t\treturn { ids: created };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}transform`,\n\t\t\tlabel: 'Transform…',\n\t\t\tkeywords: ['move', 'translate', 'scale', 'resize', 'rotate', 'flip'],\n\t\t\tdescription:\n\t\t\t\t'Moves (dx, dy), scales (sx, sy, along the selection box axes) and/or rotates (`rotate`: by degrees clockwise around the combined centre; `angle`: to an absolute rotation, each node around its own centre), in that order.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tids: IDS,\n\t\t\t\t\tdx: { type: 'number' },\n\t\t\t\t\tdy: { type: 'number' },\n\t\t\t\t\tsx: { type: 'number' },\n\t\t\t\t\tsy: { type: 'number' },\n\t\t\t\t\trotate: { type: 'number' },\n\t\t\t\t\tangle: { type: 'number' }\n\t\t\t\t}\n\t\t\t},\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[]; dx?: number; dy?: number; sx?: number; sy?: number; rotate?: number; angle?: number }) => {\n\t\t\t\tconst target = ids(params);\n\t\t\t\tif (params.dx || params.dy) check(controller.translate(params.dx ?? 0, params.dy ?? 0, { ids: target }));\n\t\t\t\tif (params.sx !== undefined || params.sy !== undefined) check(controller.scale(params.sx ?? 1, params.sy ?? params.sx ?? 1, { ids: target }));\n\t\t\t\tif (params.rotate) check(controller.rotate(params.rotate, { ids: target }));\n\t\t\t\tif (params.angle !== undefined) check(controller.setRotation(params.angle, { ids: target }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}delete`,\n\t\t\tlabel: 'Delete',\n\t\t\tkeywords: ['remove'],\n\t\t\tdescription: 'Deletes nodes.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tcheck(controller.removeNodes(ids(params)));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}boolean`,\n\t\t\tlabel: 'Boolean Operation…',\n\t\t\tkeywords: ['union', 'unite', 'subtract', 'intersect', 'exclude', 'combine', 'pathfinder'],\n\t\t\tdescription:\n\t\t\t\t'Combines two or more paths with union, subtract (upper shapes from the lowest), intersect or exclude. With `live`, the shapes stay editable operands of a live boolean group instead (a lone live group gets the operation; its operands get their own).',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\toperation: { type: 'string', enum: ['union', 'subtract', 'intersect', 'exclude'] },\n\t\t\t\t\tids: IDS,\n\t\t\t\t\tlive: { type: 'boolean', description: 'Keep the operands editable in a live boolean group (default false: one new path).' }\n\t\t\t\t},\n\t\t\t\trequired: ['operation']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { operation: VectorBooleanOperation; ids?: string[]; live?: boolean }) => {\n\t\t\t\tconst id = controller.booleanOp(params.operation, { ids: ids(params), live: params.live });\n\t\t\t\tif (!id) fail(params.live ? 'Select two or more nodes with one parent, a live boolean group or its operands.' : 'Select at least two overlapping paths (the result was empty).');\n\t\t\t\treturn { id };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}addEffect`,\n\t\t\tlabel: 'Add Path Effect…',\n\t\t\tkeywords: ['effect', 'trim', 'round corners', 'pucker', 'bloat', 'twist', 'zig zag', 'wiggle', 'offset', 'merge', 'repeater', 'appearance', 'live'],\n\t\t\tdescription: 'Adds a live path effect to the bottom of the effect stack of nodes (paths or groups). Effects apply top to bottom; effects after a repeater act on its copies. Returns the new effect ids.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\ttype: { type: 'string', enum: VECTOR_EFFECT_TYPES },\n\t\t\t\t\tparams: { type: 'object', description: EFFECT_PARAMS_DESCRIPTION },\n\t\t\t\t\tindex: { type: 'integer', minimum: 0, description: 'Position in the stack (default: last).' },\n\t\t\t\t\tids: IDS\n\t\t\t\t},\n\t\t\t\trequired: ['type']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\tenabled: enabledFor,\n\t\t\trun: (_ctx: unknown, params: { type: VectorEffectType; params?: Record<string, unknown>; index?: number; ids?: string[] }) => {\n\t\t\t\tif (!VECTOR_EFFECT_TYPES.includes(params.type)) fail(`Unknown effect type \"${params.type}\".`);\n\t\t\t\tconst targets = ids(params);\n\t\t\t\tconst created = controller.addEffect(params.type, { ids: targets, params: params.params, index: params.index });\n\t\t\t\tif (!created.length) fail('Nothing to add the effect to.');\n\t\t\t\treturn { effects: targets.map((id, i) => ({ nodeId: id, effectId: created[i] })) };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}updateEffect`,\n\t\t\tlabel: 'Edit Path Effect…',\n\t\t\tkeywords: ['effect', 'parameters', 'enable', 'disable', 'reorder'],\n\t\t\tdescription: 'Changes a path effect: its parameters, whether it is enabled, and its position in the stack.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tnodeId: { type: 'string' },\n\t\t\t\t\teffectId: { type: 'string' },\n\t\t\t\t\tparams: { type: 'object', description: EFFECT_PARAMS_DESCRIPTION },\n\t\t\t\t\tenabled: { type: 'boolean' },\n\t\t\t\t\tindex: { type: 'integer', minimum: 0, description: 'Move it to this position in the stack (0 applies first).' }\n\t\t\t\t},\n\t\t\t\trequired: ['nodeId', 'effectId']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { nodeId: string; effectId: string; params?: Record<string, unknown>; enabled?: boolean; index?: number }) => {\n\t\t\t\tif (params.params !== undefined || params.enabled !== undefined) check(controller.updateEffect(params.nodeId, params.effectId, { params: params.params, enabled: params.enabled }));\n\t\t\t\tif (params.index !== undefined) check(controller.moveEffect(params.nodeId, params.effectId, params.index));\n\t\t\t\treturn { effects: controller.getNode(params.nodeId)?.effects ?? [] };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}removeEffect`,\n\t\t\tlabel: 'Remove Path Effect',\n\t\t\tkeywords: ['effect', 'delete'],\n\t\t\tdescription: 'Removes a path effect from a node.',\n\t\t\tparams: { type: 'object', properties: { nodeId: { type: 'string' }, effectId: { type: 'string' } }, required: ['nodeId', 'effectId'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { nodeId: string; effectId: string }) => {\n\t\t\t\tcheck(controller.removeEffect(params.nodeId, params.effectId));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}expandAppearance`,\n\t\t\tlabel: 'Expand Appearance',\n\t\t\tkeywords: ['effects', 'bake', 'flatten', 'live boolean', 'expand'],\n\t\t\tdescription: 'Bakes the path effects and live booleans of nodes (and everything inside them) into plain paths.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tundoable: true,\n\t\t\tenabled: enabledFor,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tconst expanded = controller.expandAppearance({ ids: ids(params) });\n\t\t\t\tif (!expanded.length) fail('Nothing selected has path effects or a live boolean.');\n\t\t\t\treturn { ids: expanded };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}blend`,\n\t\t\tlabel: 'Make Blend…',\n\t\t\tkeywords: ['blend', 'morph', 'tween', 'interpolate', 'in-between', 'steps', 'transition'],\n\t\t\tdescription:\n\t\t\t\t'Blends two or more paths or groups (sharing a parent; bottom to top, or in the order given with keepOrder) into a live blend group: the in-betweens morph shape (anchors matched by arc length, the start and winding that twist least, rotation-aware so turning shapes turn) and paint (OKLab colours, gradients, stroke width, opacity, width profiles), and regenerate whenever a key changes. Spacing: `steps` (count between each pair of keys), `distance` (along the spine) or `smoothColor`. `starts` maps key ids to the anchor of their first subpath the blend starts from. Returns the blend id.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tids: IDS,\n\t\t\t\t\t...BLEND_PROPS,\n\t\t\t\t\tkeepOrder: { type: 'boolean', description: 'Blend in the order of `ids` instead of stacking order.' },\n\t\t\t\t\tstarts: { type: 'object', additionalProperties: { type: 'integer', minimum: 0 }, description: 'Start anchor per key id.' }\n\t\t\t\t}\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: (VectorBlendOptions & { ids?: string[]; keepOrder?: boolean; starts?: Record<string, number> }) | undefined) => {\n\t\t\t\tcheckBlendOptions(params);\n\t\t\t\tconst id = controller.blend({ ...params, ids: ids(params) });\n\t\t\t\tif (!id) fail('Select two or more shapes that share a parent.');\n\t\t\t\treturn { id };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}blendOptions`,\n\t\t\tlabel: 'Blend Options…',\n\t\t\tkeywords: ['blend', 'steps', 'spacing', 'distance', 'smooth color', 'orientation', 'align to path'],\n\t\t\tdescription: 'Changes the spacing (steps, distance or smoothColor), step count, distance, orient-to-spine and linear morph of the given (or selected) blends; their steps regenerate.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, ...BLEND_PROPS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: (VectorBlendOptions & { ids?: string[] }) | undefined) => {\n\t\t\t\tcheckBlendOptions(params);\n\t\t\t\tconst r = controller.setBlendOptions(params ?? {}, { ids: params?.ids });\n\t\t\t\tif (!r.ok) fail(r.message);\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}expandBlend`,\n\t\t\tlabel: 'Expand Blend',\n\t\t\tkeywords: ['blend', 'expand', 'flatten', 'bake'],\n\t\t\tdescription: 'Expands the given (or selected) blends into plain groups of their keys and in-betweens (editable paths with fresh ids). Returns the group ids.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tconst groups = controller.expandBlend({ ids: params?.ids });\n\t\t\t\tif (!groups.length) fail('Select a blend to expand.');\n\t\t\t\treturn { ids: groups };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}releaseBlend`,\n\t\t\tlabel: 'Release Blend',\n\t\t\tkeywords: ['blend', 'release', 'undo blend'],\n\t\t\tdescription: 'Releases the given (or selected) blends: the in-betweens go and the keys (plus a replaced spine, as an unpainted path) take the blend\\'s place. Returns their ids.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tconst released = controller.releaseBlend({ ids: params?.ids });\n\t\t\t\tif (!released.length) fail('Select a blend to release.');\n\t\t\t\treturn { ids: released };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}replaceSpine`,\n\t\t\tlabel: 'Replace Spine',\n\t\t\tkeywords: ['blend', 'spine', 'path', 'follow', 'along path'],\n\t\t\tdescription: 'With one blend and one path given (or selected): the path becomes the blend\\'s spine (it is removed) and the keys move onto it, spread evenly by arc length; the in-betweens follow it.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tconst r = controller.replaceBlendSpine({ ids: params?.ids });\n\t\t\t\tif (!r.ok) fail(r.message);\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}pathfinder`,\n\t\t\tlabel: 'Pathfinder…',\n\t\t\tkeywords: ['divide', 'trim', 'merge', 'crop', 'outline', 'minus back', 'split', 'faces'],\n\t\t\tdescription:\n\t\t\t\t'Pathfinder over the given paths (bottom to top): divide (every face its own shape, painted like the top-most shape over it; open unfilled paths cut), trim (hidden parts removed), merge (trim, then touching parts of one fill joined), crop (the top shape crops the others), outline (outlines cut into open strokes where they meet) or minusBack (the top shape minus everything below). Results replace the paths, grouped when there are several.',\n\t\t\tparams: { type: 'object', properties: { operation: { type: 'string', enum: [...VECTOR_PATHFINDER_OPERATIONS] }, ids: IDS }, required: ['operation'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { operation: VectorPathfinderOperation; ids?: string[] }) => {\n\t\t\t\tif (!VECTOR_PATHFINDER_OPERATIONS.includes(params.operation)) fail(`Unknown Pathfinder operation \"${params.operation}\".`);\n\t\t\t\tconst created = controller.pathfinder(params.operation, { ids: ids(params) });\n\t\t\t\tif (!created.length) fail('Nothing came out: select paths that overlap (two or more, except for divide and outline).');\n\t\t\t\treturn { ids: created, selection: [...controller.getState().selection] };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}shapeBuilderFaces`,\n\t\t\tlabel: 'List Shape Builder Faces',\n\t\t\tkeywords: ['shape builder', 'regions', 'faces', 'planar'],\n\t\t\tdescription: 'Lists the faces the given paths divide the plane into: path data, area, a point inside and the ids of the paths filling each (empty when only outlines enclose it).',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => ({ faces: controller.getShapeBuilderFaces({ ids: ids(params) }) })\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}shapeBuilder`,\n\t\t\tlabel: 'Shape Builder',\n\t\t\tkeywords: ['merge faces', 'build shape', 'combine regions', 'delete region'],\n\t\t\tdescription:\n\t\t\t\t'Shape Builder gesture over the faces of the given paths: `points` is the pointer path in document units (one point is a click; [[x, y], …]; use the points from shapeBuilderFaces). Merges the faces it touches into one shape, or with `erase: true` deletes them, the pieces of unfilled open paths and the edges of other outlines (cut where paths meet) within `tolerance`; a deleted edge opens its path there, and edges bounding a deleted face go with it. Shapes covering those faces lose that area. Returns the counts of faces, line pieces and edges.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: { points: POINTS, erase: { type: 'boolean' }, tolerance: { type: 'number', minimum: 0 }, ids: IDS },\n\t\t\t\trequired: ['points']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { points: [number, number][]; erase?: boolean; tolerance?: number; ids?: string[] }) => {\n\t\t\t\tconst result = controller.shapeBuilder(params.points.map(([x, y]) => ({ x, y })), { ids: ids(params), erase: params.erase, tolerance: params.tolerance });\n\t\t\t\tcheck(result);\n\t\t\t\treturn { merged: result.merged, faces: result.faces, lines: result.lines, edges: result.edges };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}fillRegion`,\n\t\t\tlabel: 'Fill Region',\n\t\t\tkeywords: ['paint bucket', 'live paint', 'fill area', 'enclosed'],\n\t\t\tdescription:\n\t\t\t\t'Paint bucket: fills the area the visible outlines enclose around `point` (crossings split, nested shapes left as holes) with a new path in the current fill, below the selection or on top. `gap` closes gaps up to that size between loose ends.',\n\t\t\tparams: { type: 'object', properties: { point: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2, description: '[x, y] in document units.' }, gap: { type: 'number', minimum: 0 } }, required: ['point'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { point: [number, number]; gap?: number }) => {\n\t\t\t\tconst id = controller.fillRegion({ x: params.point[0], y: params.point[1] }, { gap: params.gap });\n\t\t\t\tif (!id) fail('No closed region surrounds that point.');\n\t\t\t\treturn { id };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}align`,\n\t\t\tlabel: 'Align…',\n\t\t\tkeywords: ['left', 'right', 'center', 'top', 'bottom', 'middle'],\n\t\t\tdescription: 'Aligns nodes to their combined bounds, or to the artboard (`to: \"artboard\"`, or when only one node is given).',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: { edge: { type: 'string', enum: ['left', 'center', 'right', 'top', 'middle', 'bottom'] }, to: { type: 'string', enum: ['selection', 'artboard'] }, ids: IDS },\n\t\t\t\trequired: ['edge']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { edge: VectorAlign; to?: 'selection' | 'artboard'; ids?: string[] }) => {\n\t\t\t\tcheck(controller.align(params.edge, { ids: ids(params), to: params.to }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}distribute`,\n\t\t\tlabel: 'Distribute…',\n\t\t\tkeywords: ['space', 'evenly', 'gaps'],\n\t\t\tdescription: 'Spaces three or more nodes with equal gaps.',\n\t\t\tparams: { type: 'object', properties: { direction: { type: 'string', enum: ['horizontal', 'vertical'] }, ids: IDS }, required: ['direction'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { direction: 'horizontal' | 'vertical'; ids?: string[] }) => {\n\t\t\t\tcheck(controller.distribute(params.direction, { ids: ids(params) }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}arrange`,\n\t\t\tlabel: 'Arrange…',\n\t\t\tkeywords: ['order', 'front', 'back', 'forward', 'backward', 'z-index', 'stack'],\n\t\t\tdescription: 'Changes stacking order within each node’s parent.',\n\t\t\tparams: { type: 'object', properties: { order: { type: 'string', enum: ['front', 'forward', 'backward', 'back'] }, ids: IDS }, required: ['order'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { order: VectorArrange; ids?: string[] }) => {\n\t\t\t\tcheck(controller.arrange(params.order, { ids: ids(params) }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}group`,\n\t\t\tlabel: 'Group',\n\t\t\tshortcut: 'Mod+G',\n\t\t\tdescription: 'Groups nodes that share a parent.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, name: { type: 'string' } } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[]; name?: string } | undefined) => {\n\t\t\t\tconst id = controller.group({ ids: ids(params), name: params?.name });\n\t\t\t\tif (!id) fail('Only nodes that share a parent can be grouped.');\n\t\t\t\treturn { id };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}ungroup`,\n\t\t\tlabel: 'Ungroup',\n\t\t\tshortcut: 'Mod+Shift+G',\n\t\t\tdescription: 'Replaces groups with their children.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[] } | undefined) => {\n\t\t\t\tcheck(controller.ungroup({ ids: ids(params) }));\n\t\t\t\treturn { selection: controller.getState().selection };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}deleteAnchors`,\n\t\t\tlabel: 'Delete Points',\n\t\t\tkeywords: ['remove', 'anchor', 'point', 'node'],\n\t\t\tdescription:\n\t\t\t\t'Deletes anchors. With `keepShape` (default: the editor setting, on) the neighbouring handles are refitted so the outline keeps its shape; off, the neighbours are joined as they are.',\n\t\t\tparams: { type: 'object', properties: { anchors: ANCHORS, keepShape: { type: 'boolean' } } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { anchors?: VectorAnchorRef[]; keepShape?: boolean } | undefined) => {\n\t\t\t\tcheck(controller.deleteAnchors(anchorsOf(params?.anchors), { keepShape: params?.keepShape }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}moveAnchor`,\n\t\t\tlabel: 'Move Point…',\n\t\t\tkeywords: ['anchor', 'drag', 'curvature'],\n\t\t\tdescription: 'Moves one anchor to (x, y) in document units. `curvature: true` bends its two segments like the Curvature tool instead of carrying its handles along.',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: { nodeId: { type: 'string' }, subpath: { type: 'integer', minimum: 0 }, index: { type: 'integer', minimum: 0 }, x: { type: 'number' }, y: { type: 'number' }, curvature: { type: 'boolean' } },\n\t\t\t\trequired: ['nodeId', 'subpath', 'index', 'x', 'y']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: VectorAnchorRef & { x: number; y: number; curvature?: boolean }) => {\n\t\t\t\tcheck(controller.moveAnchor({ nodeId: params.nodeId, subpath: params.subpath, index: params.index }, { x: params.x, y: params.y }, { curvature: params.curvature }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}bendSegment`,\n\t\t\tlabel: 'Bend Segment…',\n\t\t\tkeywords: ['curve', 'segment', 'drag'],\n\t\t\tdescription: 'Bends segment `segment` (from anchor `segment` to the next) so the curve passes through (x, y) at parameter `t` (default 0.5).',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tnodeId: { type: 'string' },\n\t\t\t\t\tsubpath: { type: 'integer', minimum: 0 },\n\t\t\t\t\tsegment: { type: 'integer', minimum: 0 },\n\t\t\t\t\tx: { type: 'number' },\n\t\t\t\t\ty: { type: 'number' },\n\t\t\t\t\tt: { type: 'number', minimum: 0, maximum: 1 }\n\t\t\t\t},\n\t\t\t\trequired: ['nodeId', 'subpath', 'segment', 'x', 'y']\n\t\t\t},\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { nodeId: string; subpath: number; segment: number; x: number; y: number; t?: number }) => {\n\t\t\t\tcheck(controller.bendSegment({ nodeId: params.nodeId, subpath: params.subpath, segment: params.segment }, { x: params.x, y: params.y }, params.t));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}join`,\n\t\t\tlabel: 'Join',\n\t\t\tshortcut: 'Mod+J',\n\t\t\tkeywords: ['connect', 'close', 'merge', 'endpoints'],\n\t\t\tdescription: 'Joins the open subpaths of paths nearest endpoints first (ends within `tolerance` merge); a single open path is closed.',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, tolerance: { type: 'number', minimum: 0 } } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[]; tolerance?: number } | undefined) => {\n\t\t\t\tcheck(controller.joinPaths({ ids: params?.ids ? ids(params) : undefined, tolerance: params?.tolerance }));\n\t\t\t\treturn { selection: controller.getState().selection };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}average`,\n\t\t\tlabel: 'Average Points…',\n\t\t\tshortcut: 'Mod+Alt+J',\n\t\t\tkeywords: ['align', 'points', 'anchors', 'collapse'],\n\t\t\tdescription: 'Moves anchors onto their average position: the same y (`horizontal`), the same x (`vertical`) or the same point (`both`, default).',\n\t\t\tparams: { type: 'object', properties: { axis: { type: 'string', enum: ['horizontal', 'vertical', 'both'] }, anchors: ANCHORS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { axis?: VectorAverageAxis; anchors?: VectorAnchorRef[] } | undefined) => {\n\t\t\t\tcheck(controller.averageAnchors(params?.axis ?? 'both', anchorsOf(params?.anchors)));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}removeRedundantPoints`,\n\t\t\tlabel: 'Remove Redundant Points',\n\t\t\tkeywords: ['simplify', 'clean up', 'anchors', 'collinear'],\n\t\t\tdescription: 'Drops anchors that can go without moving the outline more than `tolerance` document units (default 0.5).',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, tolerance: { type: 'number', minimum: 0 } } },\n\t\t\tenabled: enabledFor,\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[]; tolerance?: number } | undefined) => {\n\t\t\t\tcheck(controller.removeRedundantPoints({ ids: params?.ids ? ids(params) : undefined, tolerance: params?.tolerance }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}smooth`,\n\t\t\tlabel: 'Smooth Path',\n\t\t\tkeywords: ['round', 'soften', 'handles', 'curves'],\n\t\t\tdescription: 'Pulls the handles of paths (or of the selected points) towards smooth tangents by `amount` (0–1, default 1).',\n\t\t\tparams: { type: 'object', properties: { ids: IDS, amount: { type: 'number', minimum: 0, maximum: 1 }, anchors: ANCHORS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { ids?: string[]; amount?: number; anchors?: VectorAnchorRef[] } | undefined) => {\n\t\t\t\tcheck(controller.smoothPaths({ ids: params?.ids ? ids(params) : undefined, amount: params?.amount, refs: params?.anchors }));\n\t\t\t\treturn { ok: true };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}cut`,\n\t\t\tlabel: 'Cut Path (Scissors)…',\n\t\t\tkeywords: ['scissors', 'split', 'open', 'break'],\n\t\t\tdescription: 'Scissors: cuts path `nodeId` at the outline point nearest (x, y), or cuts at `anchors`. A closed path opens; an open one splits in two.',\n\t\t\tparams: { type: 'object', properties: { nodeId: { type: 'string' }, x: { type: 'number' }, y: { type: 'number' }, anchors: ANCHORS } },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { nodeId?: string; x?: number; y?: number; anchors?: VectorAnchorRef[] } | undefined) => {\n\t\t\t\tif (params?.nodeId !== undefined) {\n\t\t\t\t\tif (!controller.getNode(params.nodeId)) fail(`There is no node \"${params.nodeId}\".`);\n\t\t\t\t\tif (params.x === undefined || params.y === undefined) fail('Pass x and y with nodeId.');\n\t\t\t\t\tconst created = controller.cutPathAt(params.nodeId, { x: params.x!, y: params.y! });\n\t\t\t\t\tif (!created) fail('Nothing to cut there (the end of an open path, or a locked path).');\n\t\t\t\t\treturn { ids: created };\n\t\t\t\t}\n\t\t\t\tcheck(controller.cutAtAnchors(anchorsOf(params?.anchors)));\n\t\t\t\treturn { ids: controller.getState().selection };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}knife`,\n\t\t\tlabel: 'Knife…',\n\t\t\tkeywords: ['slice', 'cut', 'split', 'divide'],\n\t\t\tdescription: 'Cuts closed paths along a polyline wherever it crosses them edge to edge (no gap between pieces). Cuts `ids`, else the selection, else every path the line crosses.',\n\t\t\tparams: { type: 'object', properties: { points: { ...POINTS, minItems: 2 }, ids: IDS }, required: ['points'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { points: [number, number][]; ids?: string[] }) => {\n\t\t\t\tconst created = controller.knife(params.points.map(([x, y]) => ({ x, y })), { ids: params.ids ? ids(params) : undefined });\n\t\t\t\tif (!created.length) fail('The knife did not cross any closed path from edge to edge.');\n\t\t\t\treturn { ids: created };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}erase`,\n\t\t\tlabel: 'Erase…',\n\t\t\tkeywords: ['eraser', 'rub out', 'remove'],\n\t\t\tdescription:\n\t\t\t\t'Erases a round stroke of diameter `size` (default: the eraser size setting) along a polyline: filled shapes lose the area, outlines are cut under it. Erases from `ids`, else the selection, else every path it reaches.',\n\t\t\tparams: { type: 'object', properties: { points: POINTS, size: { type: 'number', exclusiveMinimum: 0 }, ids: IDS }, required: ['points'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { points: [number, number][]; size?: number; ids?: string[] }) => {\n\t\t\t\tconst changed = controller.erase(params.points.map(([x, y]) => ({ x, y })), { size: params.size, ids: params.ids ? ids(params) : undefined });\n\t\t\t\tif (!changed.length) fail('The eraser did not touch any path.');\n\t\t\t\treturn { ids: changed.filter((id) => controller.getNode(id)) };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setEditing`,\n\t\t\tlabel: 'Path Editing Options…',\n\t\t\tkeywords: ['settings', 'curvature', 'pencil', 'smoothing', 'lazy mouse', 'shapes', 'eraser'],\n\t\t\tdescription:\n\t\t\t\t'Sets how editing behaves: `keepShapeOnDelete`, `curvatureMoves` (anchor drags bend like the Curvature tool), `pencilSmoothing` (lazy mouse, 0–1), `recognizeShapes` (pencil scribbles become lines, rectangles, ellipses, polygons), `eraserSize` and `pencilPressure` (`pen`: record pen pressure on pencil strokes as a width profile, `all`: any pointer reporting pressure, `off`).',\n\t\t\tparams: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\tkeepShapeOnDelete: { type: 'boolean' },\n\t\t\t\t\tcurvatureMoves: { type: 'boolean' },\n\t\t\t\t\tpencilSmoothing: { type: 'number', minimum: 0, maximum: 1 },\n\t\t\t\t\trecognizeShapes: { type: 'boolean' },\n\t\t\t\t\teraserSize: { type: 'number', exclusiveMinimum: 0 },\n\t\t\t\t\tpencilPressure: { type: 'string', enum: ['pen', 'all', 'off'] }\n\t\t\t\t}\n\t\t\t},\n\t\t\trun: (_ctx: unknown, params: Record<string, unknown> | undefined) => {\n\t\t\t\tconst patch: Record<string, unknown> = {};\n\t\t\t\tfor (const key of ['keepShapeOnDelete', 'curvatureMoves', 'pencilSmoothing', 'recognizeShapes', 'eraserSize', 'pencilPressure']) if (params?.[key] !== undefined) patch[key] = params[key];\n\t\t\t\tcontroller.setEditing(patch);\n\t\t\t\treturn { editing: controller.getState().editing };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}setTool`,\n\t\t\tlabel: 'Choose Tool…',\n\t\t\tkeywords: ['pen', 'pencil', 'select', 'rectangle', 'ellipse'],\n\t\t\tdescription: 'Switches the canvas tool.',\n\t\t\tparams: { type: 'object', properties: { tool: { type: 'string', enum: [...VECTOR_TOOLS] } }, required: ['tool'] },\n\t\t\trun: (_ctx: unknown, params: { tool: VectorTool }) => {\n\t\t\t\tcontroller.setTool(params.tool);\n\t\t\t\treturn { tool: params.tool };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}exportSvg`,\n\t\t\tlabel: 'Export SVG',\n\t\t\treadOnly: true,\n\t\t\tkeywords: ['download', 'save', 'markup'],\n\t\t\tdescription: 'Returns the document as SVG markup.',\n\t\t\tparams: { type: 'object', properties: { precision: { type: 'integer', minimum: 0, maximum: 8 } } },\n\t\t\trun: (_ctx: unknown, params: { precision?: number } | undefined) => ({ svg: controller.toSvg({ precision: params?.precision }) })\n\t\t},\n\t\t{\n\t\t\t...base,\n\t\t\tid: `${prefix}importSvg`,\n\t\t\tlabel: 'Import SVG…',\n\t\t\tkeywords: ['open', 'load', 'paste', 'markup'],\n\t\t\tdescription: 'Adds the shapes of SVG markup on top of the document (or replaces it with `replace: true`).',\n\t\t\tparams: { type: 'object', properties: { svg: { type: 'string' }, replace: { type: 'boolean' } }, required: ['svg'] },\n\t\t\tundoable: true,\n\t\t\trun: (_ctx: unknown, params: { svg: string; replace?: boolean }) => {\n\t\t\t\tcheck(params.replace ? controller.loadSvg(params.svg) : controller.importSvg(params.svg));\n\t\t\t\treturn { nodes: controller.getState().document.nodes.length, selection: controller.getState().selection };\n\t\t\t}\n\t\t}\n\t];\n}\n"]}
|