@motionscript/renderer 0.1.0-alpha.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1893,7 +1893,7 @@ function __wbg_get_imports() {
1893
1893
  const a = state0.a;
1894
1894
  state0.a = 0;
1895
1895
  try {
1896
- return wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___js_sys_2044d24e9c2bb073___Function_fn_wasm_bindgen_1191a70febc59fc___JsValue_____wasm_bindgen_1191a70febc59fc___sys__Undefined___js_sys_2044d24e9c2bb073___Function_fn_wasm_bindgen_1191a70febc59fc___JsValue_____wasm_bindgen_1191a70febc59fc___sys__Undefined_______true_(a, state0.b, arg0, arg1);
1896
+ return wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___js_sys_aa8f45fc28b2b0aa___Function_fn_wasm_bindgen_5bb76fec55d7fe60___JsValue_____wasm_bindgen_5bb76fec55d7fe60___sys__Undefined___js_sys_aa8f45fc28b2b0aa___Function_fn_wasm_bindgen_5bb76fec55d7fe60___JsValue_____wasm_bindgen_5bb76fec55d7fe60___sys__Undefined_______true_(a, state0.b, arg0, arg1);
1897
1897
  } finally {
1898
1898
  state0.a = a;
1899
1899
  }
@@ -1922,7 +1922,7 @@ function __wbg_get_imports() {
1922
1922
  const a = state0.a;
1923
1923
  state0.a = 0;
1924
1924
  try {
1925
- return wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___js_sys_2044d24e9c2bb073___Function_fn_wasm_bindgen_1191a70febc59fc___JsValue_____wasm_bindgen_1191a70febc59fc___sys__Undefined___js_sys_2044d24e9c2bb073___Function_fn_wasm_bindgen_1191a70febc59fc___JsValue_____wasm_bindgen_1191a70febc59fc___sys__Undefined_______true_(a, state0.b, arg0, arg1);
1925
+ return wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___js_sys_aa8f45fc28b2b0aa___Function_fn_wasm_bindgen_5bb76fec55d7fe60___JsValue_____wasm_bindgen_5bb76fec55d7fe60___sys__Undefined___js_sys_aa8f45fc28b2b0aa___Function_fn_wasm_bindgen_5bb76fec55d7fe60___JsValue_____wasm_bindgen_5bb76fec55d7fe60___sys__Undefined_______true_(a, state0.b, arg0, arg1);
1926
1926
  } finally {
1927
1927
  state0.a = a;
1928
1928
  }
@@ -2657,19 +2657,19 @@ function __wbg_get_imports() {
2657
2657
  }, arguments);
2658
2658
  },
2659
2659
  __wbindgen_generic_0000000000000001: function(arg0, arg1) {
2660
- return makeMutClosure(arg0, arg1, wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___JsValue__core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true_);
2660
+ return makeMutClosure(arg0, arg1, wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___JsValue__core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true_);
2661
2661
  },
2662
2662
  __wbindgen_generic_0000000000000002: function(arg0, arg1) {
2663
- return makeMutClosure(arg0, arg1, wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___sys__JsNullable_wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true_);
2663
+ return makeMutClosure(arg0, arg1, wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___sys__JsNullable_wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true_);
2664
2664
  },
2665
2665
  __wbindgen_generic_0000000000000003: function(arg0, arg1) {
2666
- return makeMutClosure(arg0, arg1, wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuDeviceLostInfo__GpuDeviceLostInfo______true_);
2666
+ return makeMutClosure(arg0, arg1, wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuDeviceLostInfo__GpuDeviceLostInfo______true_);
2667
2667
  },
2668
2668
  __wbindgen_generic_0000000000000004: function(arg0, arg1) {
2669
- return makeMutClosure(arg0, arg1, wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___sys__JsNullable_wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true__46);
2669
+ return makeMutClosure(arg0, arg1, wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___sys__JsNullable_wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true__46);
2670
2670
  },
2671
2671
  __wbindgen_generic_0000000000000005: function(arg0, arg1) {
2672
- return makeMutClosure(arg0, arg1, wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___sys__JsNullable_wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true__47);
2672
+ return makeMutClosure(arg0, arg1, wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___sys__JsNullable_wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true__47);
2673
2673
  },
2674
2674
  __wbindgen_generic_0000000000000006: function(arg0) {
2675
2675
  return arg0;
@@ -2691,27 +2691,27 @@ function __wbg_get_imports() {
2691
2691
  }
2692
2692
  };
2693
2693
  }
2694
- function wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuDeviceLostInfo__GpuDeviceLostInfo______true_(arg0, arg1, arg2) {
2695
- wasm$1.wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuDeviceLostInfo__GpuDeviceLostInfo______true_(arg0, arg1, arg2);
2694
+ function wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuDeviceLostInfo__GpuDeviceLostInfo______true_(arg0, arg1, arg2) {
2695
+ wasm$1.wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuDeviceLostInfo__GpuDeviceLostInfo______true_(arg0, arg1, arg2);
2696
2696
  }
2697
- function wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___JsValue__core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true_(arg0, arg1, arg2) {
2698
- const ret = wasm$1.wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___JsValue__core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true_(arg0, arg1, arg2);
2697
+ function wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___JsValue__core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true_(arg0, arg1, arg2) {
2698
+ const ret = wasm$1.wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___JsValue__core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true_(arg0, arg1, arg2);
2699
2699
  if (ret[1]) throw takeFromExternrefTable0(ret[0]);
2700
2700
  }
2701
- function wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___sys__JsNullable_wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true_(arg0, arg1, arg2) {
2702
- const ret = wasm$1.wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___sys__JsNullable_wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true_(arg0, arg1, arg2);
2701
+ function wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___sys__JsNullable_wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true_(arg0, arg1, arg2) {
2702
+ const ret = wasm$1.wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___sys__JsNullable_wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true_(arg0, arg1, arg2);
2703
2703
  if (ret[1]) throw takeFromExternrefTable0(ret[0]);
2704
2704
  }
2705
- function wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___sys__JsNullable_wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true__46(arg0, arg1, arg2) {
2706
- const ret = wasm$1.wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___sys__JsNullable_wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true__46(arg0, arg1, arg2);
2705
+ function wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___sys__JsNullable_wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true__46(arg0, arg1, arg2) {
2706
+ const ret = wasm$1.wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___sys__JsNullable_wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true__46(arg0, arg1, arg2);
2707
2707
  if (ret[1]) throw takeFromExternrefTable0(ret[0]);
2708
2708
  }
2709
- function wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___sys__JsNullable_wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true__47(arg0, arg1, arg2) {
2710
- const ret = wasm$1.wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___wasm_bindgen_1191a70febc59fc___sys__JsNullable_wgpu_1da0beaa27c83114___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_1191a70febc59fc___JsError___true__47(arg0, arg1, arg2);
2709
+ function wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___sys__JsNullable_wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true__47(arg0, arg1, arg2) {
2710
+ const ret = wasm$1.wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___wasm_bindgen_5bb76fec55d7fe60___sys__JsNullable_wgpu_86078daaf2a035a0___backend__webgpu__webgpu_sys__gen_GpuError__GpuError___core_37ee0ce0c4cd57d5___result__Result_____wasm_bindgen_5bb76fec55d7fe60___JsError___true__47(arg0, arg1, arg2);
2711
2711
  if (ret[1]) throw takeFromExternrefTable0(ret[0]);
2712
2712
  }
2713
- function wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___js_sys_2044d24e9c2bb073___Function_fn_wasm_bindgen_1191a70febc59fc___JsValue_____wasm_bindgen_1191a70febc59fc___sys__Undefined___js_sys_2044d24e9c2bb073___Function_fn_wasm_bindgen_1191a70febc59fc___JsValue_____wasm_bindgen_1191a70febc59fc___sys__Undefined_______true_(arg0, arg1, arg2, arg3) {
2714
- wasm$1.wasm_bindgen_1191a70febc59fc___convert__closures_____invoke___js_sys_2044d24e9c2bb073___Function_fn_wasm_bindgen_1191a70febc59fc___JsValue_____wasm_bindgen_1191a70febc59fc___sys__Undefined___js_sys_2044d24e9c2bb073___Function_fn_wasm_bindgen_1191a70febc59fc___JsValue_____wasm_bindgen_1191a70febc59fc___sys__Undefined_______true_(arg0, arg1, arg2, arg3);
2713
+ function wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___js_sys_aa8f45fc28b2b0aa___Function_fn_wasm_bindgen_5bb76fec55d7fe60___JsValue_____wasm_bindgen_5bb76fec55d7fe60___sys__Undefined___js_sys_aa8f45fc28b2b0aa___Function_fn_wasm_bindgen_5bb76fec55d7fe60___JsValue_____wasm_bindgen_5bb76fec55d7fe60___sys__Undefined_______true_(arg0, arg1, arg2, arg3) {
2714
+ wasm$1.wasm_bindgen_5bb76fec55d7fe60___convert__closures_____invoke___js_sys_aa8f45fc28b2b0aa___Function_fn_wasm_bindgen_5bb76fec55d7fe60___JsValue_____wasm_bindgen_5bb76fec55d7fe60___sys__Undefined___js_sys_aa8f45fc28b2b0aa___Function_fn_wasm_bindgen_5bb76fec55d7fe60___JsValue_____wasm_bindgen_5bb76fec55d7fe60___sys__Undefined_______true_(arg0, arg1, arg2, arg3);
2715
2715
  }
2716
2716
  var __wbindgen_enum_GpuAddressMode = [
2717
2717
  "clamp-to-edge",
@@ -8242,6 +8242,222 @@ function dashedPath(cs, dash, offset, dots, fit) {
8242
8242
  return any ? b.build() : null;
8243
8243
  }
8244
8244
  //#endregion
8245
+ //#region web/src/adapter/svg-path.ts
8246
+ /** Endpoint-parameterised arc → cubic segments (SVG implementation notes F.6). */
8247
+ function arcTo(b, x1, y1, rx, ry, phi, large, sweep, x2, y2) {
8248
+ if (rx === 0 || ry === 0) {
8249
+ b.lineTo(x2, y2);
8250
+ return;
8251
+ }
8252
+ rx = Math.abs(rx);
8253
+ ry = Math.abs(ry);
8254
+ const c = Math.cos(phi), s = Math.sin(phi);
8255
+ const dx = (x1 - x2) / 2, dy = (y1 - y2) / 2;
8256
+ const xp = c * dx + s * dy, yp = -s * dx + c * dy;
8257
+ const lam = xp * xp / (rx * rx) + yp * yp / (ry * ry);
8258
+ if (lam > 1) {
8259
+ rx *= Math.sqrt(lam);
8260
+ ry *= Math.sqrt(lam);
8261
+ }
8262
+ const num = rx * rx * ry * ry - rx * rx * yp * yp - ry * ry * xp * xp;
8263
+ const den = rx * rx * yp * yp + ry * ry * xp * xp;
8264
+ let k = Math.sqrt(Math.max(0, num / den));
8265
+ if (large === sweep) k = -k;
8266
+ const cxp = k * rx * yp / ry, cyp = -k * ry * xp / rx;
8267
+ const cx = c * cxp - s * cyp + (x1 + x2) / 2, cy = s * cxp + c * cyp + (y1 + y2) / 2;
8268
+ const ang = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
8269
+ const t1 = ang(1, 0, (xp - cxp) / rx, (yp - cyp) / ry);
8270
+ let dt = ang((xp - cxp) / rx, (yp - cyp) / ry, (-xp - cxp) / rx, (-yp - cyp) / ry);
8271
+ if (!sweep && dt > 0) dt -= 2 * Math.PI;
8272
+ else if (sweep && dt < 0) dt += 2 * Math.PI;
8273
+ const n = Math.ceil(Math.abs(dt) / (Math.PI / 2) - 1e-9);
8274
+ const seg = dt / n;
8275
+ const alpha = 4 / 3 * Math.tan(seg / 4);
8276
+ for (let i = 0; i < n; i++) {
8277
+ const ta = t1 + i * seg, tb = ta + seg;
8278
+ const cosA = Math.cos(ta), sinA = Math.sin(ta), cosB = Math.cos(tb), sinB = Math.sin(tb);
8279
+ const ax = cx + rx * cosA * c - ry * sinA * s, ay = cy + rx * cosA * s + ry * sinA * c;
8280
+ const bx = cx + rx * cosB * c - ry * sinB * s, by = cy + rx * cosB * s + ry * sinB * c;
8281
+ const dax = -rx * sinA * c - ry * cosA * s, day = -rx * sinA * s + ry * cosA * c;
8282
+ const dbx = -rx * sinB * c - ry * cosB * s, dby = -rx * sinB * s + ry * cosB * c;
8283
+ b.cubicTo(ax + alpha * dax, ay + alpha * day, bx - alpha * dbx, by - alpha * dby, i === n - 1 ? x2 : bx, i === n - 1 ? y2 : by);
8284
+ }
8285
+ }
8286
+ function parseSvgPath(d) {
8287
+ const b = new PathBuilder();
8288
+ const tokens = d.match(/[a-zA-Z]|-?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/g) ?? [];
8289
+ let i = 0;
8290
+ let cmd = "";
8291
+ let x = 0, y = 0, sx = 0, sy = 0;
8292
+ let lcx = 0, lcy = 0, lqx = 0, lqy = 0;
8293
+ let prev = "";
8294
+ const num = () => Number(tokens[i++]);
8295
+ const more = () => i < tokens.length && !/[a-zA-Z]/.test(tokens[i]);
8296
+ while (i < tokens.length) {
8297
+ if (/[a-zA-Z]/.test(tokens[i])) cmd = tokens[i++];
8298
+ const rel = cmd === cmd.toLowerCase();
8299
+ const C = cmd.toUpperCase();
8300
+ const ox = rel ? x : 0, oy = rel ? y : 0;
8301
+ switch (C) {
8302
+ case "M":
8303
+ x = ox + num();
8304
+ y = oy + num();
8305
+ b.moveTo(x, y);
8306
+ sx = x;
8307
+ sy = y;
8308
+ cmd = rel ? "l" : "L";
8309
+ break;
8310
+ case "L":
8311
+ x = ox + num();
8312
+ y = oy + num();
8313
+ b.lineTo(x, y);
8314
+ break;
8315
+ case "H":
8316
+ x = (rel ? x : 0) + num();
8317
+ b.lineTo(x, y);
8318
+ break;
8319
+ case "V":
8320
+ y = (rel ? y : 0) + num();
8321
+ b.lineTo(x, y);
8322
+ break;
8323
+ case "C": {
8324
+ const x1 = ox + num(), y1 = oy + num(), x2 = ox + num(), y2 = oy + num();
8325
+ x = ox + num();
8326
+ y = oy + num();
8327
+ b.cubicTo(x1, y1, x2, y2, x, y);
8328
+ lcx = x2;
8329
+ lcy = y2;
8330
+ break;
8331
+ }
8332
+ case "S": {
8333
+ const r = prev === "C" || prev === "S";
8334
+ const x1 = r ? 2 * x - lcx : x, y1 = r ? 2 * y - lcy : y;
8335
+ const x2 = ox + num(), y2 = oy + num();
8336
+ x = ox + num();
8337
+ y = oy + num();
8338
+ b.cubicTo(x1, y1, x2, y2, x, y);
8339
+ lcx = x2;
8340
+ lcy = y2;
8341
+ break;
8342
+ }
8343
+ case "Q":
8344
+ lqx = ox + num();
8345
+ lqy = oy + num();
8346
+ x = ox + num();
8347
+ y = oy + num();
8348
+ b.quadTo(lqx, lqy, x, y);
8349
+ break;
8350
+ case "T": {
8351
+ const r = prev === "Q" || prev === "T";
8352
+ lqx = r ? 2 * x - lqx : x;
8353
+ lqy = r ? 2 * y - lqy : y;
8354
+ x = ox + num();
8355
+ y = oy + num();
8356
+ b.quadTo(lqx, lqy, x, y);
8357
+ break;
8358
+ }
8359
+ case "A": {
8360
+ const rx = num(), ry = num(), phi = num() * Math.PI / 180, large = num() !== 0, sweep = num() !== 0;
8361
+ const nx = ox + num(), ny = oy + num();
8362
+ arcTo(b, x, y, rx, ry, phi, large, sweep, nx, ny);
8363
+ x = nx;
8364
+ y = ny;
8365
+ break;
8366
+ }
8367
+ case "Z":
8368
+ b.close();
8369
+ x = sx;
8370
+ y = sy;
8371
+ break;
8372
+ default: i++;
8373
+ }
8374
+ prev = C;
8375
+ if (C === "Z" && more()) cmd = "L";
8376
+ }
8377
+ return b.build();
8378
+ }
8379
+ //#endregion
8380
+ //#region web/src/adapter/geometry/sink.ts
8381
+ var SvgSink = class {
8382
+ parts = [];
8383
+ move(x, y) {
8384
+ this.parts.push(`M ${x} ${y}`);
8385
+ }
8386
+ line(x, y) {
8387
+ this.parts.push(`L ${x} ${y}`);
8388
+ }
8389
+ arc(rx, ry, large, sweep, x, y) {
8390
+ this.parts.push(`A ${rx} ${ry} 0 ${large} ${sweep} ${x} ${y}`);
8391
+ }
8392
+ close() {
8393
+ this.parts.push("Z");
8394
+ }
8395
+ toString() {
8396
+ return this.parts.join(" ");
8397
+ }
8398
+ };
8399
+ /**
8400
+ * The outline as `parseSvgPath` reads it from {@link SvgSink}'s string, without
8401
+ * the string. A finite number prints and parses back to itself, and the parser
8402
+ * adds an absolute coordinate to 0 (so −0 lands as 0); a number that is not
8403
+ * finite does not survive the tokenizer, so `ok` turns false and the caller
8404
+ * takes the string route.
8405
+ */
8406
+ var PathDataSink = class {
8407
+ ok = true;
8408
+ b = new PathBuilder();
8409
+ empty = true;
8410
+ x = 0;
8411
+ y = 0;
8412
+ sx = 0;
8413
+ sy = 0;
8414
+ move(x, y) {
8415
+ this.check(x, y);
8416
+ this.x = this.sx = 0 + x;
8417
+ this.y = this.sy = 0 + y;
8418
+ this.b.moveTo(this.x, this.y);
8419
+ }
8420
+ line(x, y) {
8421
+ this.check(x, y);
8422
+ this.x = 0 + x;
8423
+ this.y = 0 + y;
8424
+ this.b.lineTo(this.x, this.y);
8425
+ }
8426
+ arc(rx, ry, large, sweep, x, y) {
8427
+ this.check(rx, ry, x, y);
8428
+ const nx = 0 + x, ny = 0 + y;
8429
+ arcTo(this.b, this.x, this.y, rx + 0, ry + 0, 0, large !== 0, sweep !== 0, nx, ny);
8430
+ this.x = nx;
8431
+ this.y = ny;
8432
+ }
8433
+ close() {
8434
+ this.empty = false;
8435
+ this.b.close();
8436
+ this.x = this.sx;
8437
+ this.y = this.sy;
8438
+ }
8439
+ result() {
8440
+ return this.empty ? null : this.b.build();
8441
+ }
8442
+ check(...values) {
8443
+ this.empty = false;
8444
+ for (const v of values) if (!Number.isFinite(v)) this.ok = false;
8445
+ }
8446
+ };
8447
+ function svgOf(emit) {
8448
+ const sink = new SvgSink();
8449
+ emit(sink);
8450
+ return sink.toString();
8451
+ }
8452
+ /** What `parseSvgPath(svgOf(emit))` gives (null for an empty string), built directly. */
8453
+ function pathDataOf(emit) {
8454
+ const sink = new PathDataSink();
8455
+ emit(sink);
8456
+ if (sink.ok) return sink.result();
8457
+ const svg = svgOf(emit);
8458
+ return svg ? parseSvgPath(svg) : null;
8459
+ }
8460
+ //#endregion
8245
8461
  //#region web/src/adapter/geometry/base.ts
8246
8462
  var ShapeGeometry = class {
8247
8463
  fullState;
@@ -8249,6 +8465,11 @@ var ShapeGeometry = class {
8249
8465
  constructor(state) {
8250
8466
  this.fullState = this.resolveState(state);
8251
8467
  }
8468
+ /** `buildSVGPath` parsed, or null where it is empty. A shape writing to a {@link PathSink} skips the string. */
8469
+ buildPathData(geo) {
8470
+ const svg = this.buildSVGPath(geo);
8471
+ return svg ? parseSvgPath(svg) : null;
8472
+ }
8252
8473
  get geometry() {
8253
8474
  return this.geo ??= this.computeGeometry();
8254
8475
  }
@@ -8287,24 +8508,29 @@ function cornerReach(c) {
8287
8508
  return c.radius > 0 ? c.radius : 0;
8288
8509
  }
8289
8510
  /**
8290
- * SVG commands for a right-angle (rect) corner, the pen already at its entry
8291
- * point `apex + d0·radius`; they leave it at `apex + d1·radius`. `d0` points
8511
+ * A right-angle (rect) corner, the pen already at its entry point
8512
+ * `apex + d0·radius`; it leaves the pen at `apex + d1·radius`. `d0` points
8292
8513
  * from the apex back along the incoming edge, `d1` along the outgoing one.
8293
- * Empty for a zero radius (the edge runs on to a sharp apex).
8514
+ * Nothing for a zero radius (the edge runs on to a sharp apex).
8294
8515
  */
8295
- function cornerCommands(ax, ay, d0x, d0y, d1x, d1y, c) {
8296
- if (c.radius <= 0) return "";
8297
- return cornerCommandsFromCut(ax, ay, d0x, d0y, d1x, d1y, c.radius, c.radius, c.style);
8516
+ function cornerTo(sink, ax, ay, d0x, d0y, d1x, d1y, c) {
8517
+ if (c.radius <= 0) return;
8518
+ cutCornerTo(sink, ax, ay, d0x, d0y, d1x, d1y, c.radius, c.radius, c.style);
8298
8519
  }
8299
8520
  /**
8300
- * SVG commands for a corner of any angle, given the cut-back distance `cut`
8301
- * along each edge and the arc `radius` (they differ off a right angle). The pen
8302
- * is at `apex + d0·cut`; the commands leave it at `apex + d1·cut`.
8521
+ * A corner of any angle, given the cut-back distance `cut` along each edge and
8522
+ * the arc `radius` (they differ off a right angle). The pen is at
8523
+ * `apex + d0·cut`; it is left at `apex + d1·cut`.
8303
8524
  */
8304
- function cornerCommandsFromCut(ax, ay, d0x, d0y, d1x, d1y, cut, radius, style) {
8525
+ function cutCornerTo(sink, ax, ay, d0x, d0y, d1x, d1y, cut, radius, style) {
8305
8526
  const ex = ax + d1x * cut, ey = ay + d1y * cut;
8306
- if (style === "angled") return `L ${ex} ${ey}`;
8307
- return `A ${radius} ${radius} 0 0 ${d0x * d1y - d0y * d1x < 0 ? 1 : 0} ${ex} ${ey}`;
8527
+ if (style === "angled") return sink.line(ex, ey);
8528
+ const cross = d0x * d1y - d0y * d1x;
8529
+ sink.arc(radius, radius, 0, cross < 0 ? 1 : 0, ex, ey);
8530
+ }
8531
+ /** {@link cutCornerTo} as SVG commands. */
8532
+ function cornerCommandsFromCut(ax, ay, d0x, d0y, d1x, d1y, cut, radius, style) {
8533
+ return svgOf((sink) => cutCornerTo(sink, ax, ay, d0x, d0y, d1x, d1y, cut, radius, style));
8308
8534
  }
8309
8535
  //#endregion
8310
8536
  //#region web/src/adapter/geometry/ellipse.ts
@@ -8333,21 +8559,19 @@ var EllipseGeometry = class extends ShapeGeometry {
8333
8559
  };
8334
8560
  }
8335
8561
  buildSVGPath(geo) {
8336
- const { cx, cy, halfWidth, halfHeight, ratio } = geo;
8337
- if (ratio >= 1) return "";
8338
- if (!geo.isFullShape) return ratio <= 0 ? wedge(geo) : annularSector(geo);
8339
- const outer = fullEllipse(cx, cy, halfWidth, halfHeight, 0);
8340
- if (ratio <= 0) return outer;
8341
- return `${outer} ${fullEllipse(cx, cy, halfWidth * ratio, halfHeight * ratio, 1)}`;
8562
+ return svgOf((sink) => outline$1(sink, geo));
8563
+ }
8564
+ buildPathData(geo) {
8565
+ return pathDataOf((sink) => outline$1(sink, geo));
8342
8566
  }
8343
8567
  /** What a ratio-1 ellipse strokes, once: the bare outline or open arc. Null below ratio 1. */
8344
8568
  ringOutline(geo) {
8345
8569
  if (geo.ratio < 1) return null;
8346
- return geo.isFullShape ? fullEllipse(geo.cx, geo.cy, geo.halfWidth, geo.halfHeight, 0) : openArc(geo);
8570
+ return svgOf((sink) => geo.isFullShape ? fullEllipse(sink, geo.cx, geo.cy, geo.halfWidth, geo.halfHeight, 0) : openArc(sink, geo));
8347
8571
  }
8348
8572
  buildSVGAlignInterior(geo) {
8349
- if (geo.isFullShape) return geo.ratio >= 1 ? fullEllipse(geo.cx, geo.cy, geo.halfWidth, geo.halfHeight, 0) : null;
8350
- return geo.ratio >= 1 || geo.ratio <= 0 ? wedge(geo) : annularSector(geo);
8573
+ if (geo.isFullShape) return geo.ratio >= 1 ? svgOf((sink) => fullEllipse(sink, geo.cx, geo.cy, geo.halfWidth, geo.halfHeight, 0)) : null;
8574
+ return svgOf((sink) => geo.ratio >= 1 || geo.ratio <= 0 ? wedge(sink, geo) : annularSector(sink, geo));
8351
8575
  }
8352
8576
  supportsSpread() {
8353
8577
  return true;
@@ -8356,7 +8580,7 @@ var EllipseGeometry = class extends ShapeGeometry {
8356
8580
  if (!geo.isFullShape || geo.ratio >= 1) return null;
8357
8581
  const halfWidth = geo.halfWidth + spread, halfHeight = geo.halfHeight + spread;
8358
8582
  if (halfWidth <= 0 || halfHeight <= 0) return null;
8359
- return fullEllipse(geo.cx, geo.cy, halfWidth, halfHeight, 0);
8583
+ return svgOf((sink) => fullEllipse(sink, geo.cx, geo.cy, halfWidth, halfHeight, 0));
8360
8584
  }
8361
8585
  computeBounds(geo) {
8362
8586
  const { cx, cy, halfWidth, halfHeight } = geo;
@@ -8368,11 +8592,21 @@ var EllipseGeometry = class extends ShapeGeometry {
8368
8592
  };
8369
8593
  }
8370
8594
  };
8371
- function fullEllipse(cx, cy, rx, ry, sweepFlag) {
8372
- const left = cx - rx;
8373
- return `M ${left} ${cy} A ${rx} ${ry} 0 1 ${sweepFlag} ${cx + rx} ${cy} A ${rx} ${ry} 0 1 ${sweepFlag} ${left} ${cy} Z`;
8374
- }
8375
- /** A partial sweep's outer curve: its start point, and the `A` that runs it round. */
8595
+ function outline$1(sink, geo) {
8596
+ const { cx, cy, halfWidth, halfHeight, ratio } = geo;
8597
+ if (ratio >= 1) return;
8598
+ if (!geo.isFullShape) return ratio <= 0 ? wedge(sink, geo) : annularSector(sink, geo);
8599
+ fullEllipse(sink, cx, cy, halfWidth, halfHeight, 0);
8600
+ if (ratio > 0) fullEllipse(sink, cx, cy, halfWidth * ratio, halfHeight * ratio, 1);
8601
+ }
8602
+ function fullEllipse(sink, cx, cy, rx, ry, sweepFlag) {
8603
+ const left = cx - rx, right = cx + rx;
8604
+ sink.move(left, cy);
8605
+ sink.arc(rx, ry, 1, sweepFlag, right, cy);
8606
+ sink.arc(rx, ry, 1, sweepFlag, left, cy);
8607
+ sink.close();
8608
+ }
8609
+ /** A partial sweep's outer curve: its start point, and the arc that runs it round. */
8376
8610
  function outerArc(geo) {
8377
8611
  const { cx, cy, halfWidth, halfHeight, startAngle, sweep } = geo;
8378
8612
  const endAngle = startAngle + sweep;
@@ -8381,26 +8615,38 @@ function outerArc(geo) {
8381
8615
  const sx = cx + halfWidth * Math.cos(startAngle * TO_RAD), sy = cy + halfHeight * Math.sin(startAngle * TO_RAD);
8382
8616
  const ex = cx + halfWidth * Math.cos(endAngle * TO_RAD), ey = cy + halfHeight * Math.sin(endAngle * TO_RAD);
8383
8617
  return {
8384
- start: `${sx} ${sy}`,
8385
- arc: `A ${halfWidth} ${halfHeight} 0 ${largeArc} ${sweepFlag} ${ex} ${ey}`
8618
+ sx,
8619
+ sy,
8620
+ arc: (sink) => sink.arc(halfWidth, halfHeight, largeArc, sweepFlag, ex, ey)
8386
8621
  };
8387
8622
  }
8388
- function openArc(geo) {
8389
- const { start, arc } = outerArc(geo);
8390
- return `M ${start} ${arc}`;
8623
+ function openArc(sink, geo) {
8624
+ const { sx, sy, arc } = outerArc(geo);
8625
+ sink.move(sx, sy);
8626
+ arc(sink);
8391
8627
  }
8392
- function wedge(geo) {
8393
- const { start, arc } = outerArc(geo);
8394
- return `M ${geo.cx} ${geo.cy} L ${start} ${arc} Z`;
8628
+ function wedge(sink, geo) {
8629
+ const { sx, sy, arc } = outerArc(geo);
8630
+ sink.move(geo.cx, geo.cy);
8631
+ sink.line(sx, sy);
8632
+ arc(sink);
8633
+ sink.close();
8395
8634
  }
8396
8635
  /** Out along the outer arc, in to the inner edge, back along the inner arc, closed along the start radius. */
8397
- function annularSector(geo) {
8636
+ function annularSector(sink, geo) {
8398
8637
  const { cx, cy, halfWidth, halfHeight, startAngle, sweep, ratio } = geo;
8399
- const { start, arc } = outerArc(geo);
8638
+ const { sx, sy, arc } = outerArc(geo);
8400
8639
  const endAngle = startAngle + sweep;
8401
8640
  const innerW = halfWidth * ratio, innerH = halfHeight * ratio;
8402
8641
  const isx = cx + innerW * Math.cos(startAngle * TO_RAD), isy = cy + innerH * Math.sin(startAngle * TO_RAD);
8403
- return `M ${start} ${arc} L ${cx + innerW * Math.cos(endAngle * TO_RAD)} ${cy + innerH * Math.sin(endAngle * TO_RAD)} A ${innerW} ${innerH} 0 ${Math.abs(sweep) > 180 ? 1 : 0} ${sweep > 0 ? 0 : 1} ${isx} ${isy} Z`;
8642
+ const iex = cx + innerW * Math.cos(endAngle * TO_RAD), iey = cy + innerH * Math.sin(endAngle * TO_RAD);
8643
+ const largeArc = Math.abs(sweep) > 180 ? 1 : 0;
8644
+ const innerSweepFlag = sweep > 0 ? 0 : 1;
8645
+ sink.move(sx, sy);
8646
+ arc(sink);
8647
+ sink.line(iex, iey);
8648
+ sink.arc(innerW, innerH, largeArc, innerSweepFlag, isx, isy);
8649
+ sink.close();
8404
8650
  }
8405
8651
  //#endregion
8406
8652
  //#region web/src/adapter/geometry/line.ts
@@ -8596,22 +8842,31 @@ var PolygramGeometry = class extends ShapeGeometry {
8596
8842
  //#endregion
8597
8843
  //#region web/src/adapter/geometry/rect.ts
8598
8844
  /** The outline clockwise, each corner its own arc or chamfer `radius` px from the apex. */
8599
- function rectToSvg(l, t, r, b, tl, tr, br, bl) {
8845
+ function roundedRect(sink, l, t, r, b, tl, tr, br, bl) {
8600
8846
  const rTL = cornerReach(tl), rTR = cornerReach(tr), rBR = cornerReach(br), rBL = cornerReach(bl);
8601
- return [
8602
- `M ${l + rTL} ${t}`,
8603
- `L ${r - rTR} ${t}`,
8604
- cornerCommands(r, t, -1, 0, 0, 1, tr),
8605
- `L ${r} ${b - rBR}`,
8606
- cornerCommands(r, b, 0, -1, -1, 0, br),
8607
- `L ${l + rBL} ${b}`,
8608
- cornerCommands(l, b, 1, 0, 0, -1, bl),
8609
- `L ${l} ${t + rTL}`,
8610
- cornerCommands(l, t, 0, 1, 1, 0, tl),
8611
- "Z"
8612
- ].filter(Boolean).join(" ");
8613
- }
8614
- var plainRect = (l, t, r, b) => `M ${l} ${t} L ${r} ${t} L ${r} ${b} L ${l} ${b} Z`;
8847
+ sink.move(l + rTL, t);
8848
+ sink.line(r - rTR, t);
8849
+ cornerTo(sink, r, t, -1, 0, 0, 1, tr);
8850
+ sink.line(r, b - rBR);
8851
+ cornerTo(sink, r, b, 0, -1, -1, 0, br);
8852
+ sink.line(l + rBL, b);
8853
+ cornerTo(sink, l, b, 1, 0, 0, -1, bl);
8854
+ sink.line(l, t + rTL);
8855
+ cornerTo(sink, l, t, 0, 1, 1, 0, tl);
8856
+ sink.close();
8857
+ }
8858
+ function plainRect(sink, l, t, r, b) {
8859
+ sink.move(l, t);
8860
+ sink.line(r, t);
8861
+ sink.line(r, b);
8862
+ sink.line(l, b);
8863
+ sink.close();
8864
+ }
8865
+ function outline(sink, geo) {
8866
+ const { left, top, right, bottom, tl, tr, br, bl, isZero } = geo;
8867
+ if (isZero) plainRect(sink, left, top, right, bottom);
8868
+ else roundedRect(sink, left, top, right, bottom, tl, tr, br, bl);
8869
+ }
8615
8870
  /** Per-corner radii and styles, scaled down together where adjacent corners would overrun a side. */
8616
8871
  var RectGeometry = class extends ShapeGeometry {
8617
8872
  resolveState(state) {
@@ -8662,8 +8917,10 @@ var RectGeometry = class extends ShapeGeometry {
8662
8917
  };
8663
8918
  }
8664
8919
  buildSVGPath(geo) {
8665
- const { left, top, right, bottom, tl, tr, br, bl, isZero } = geo;
8666
- return isZero ? plainRect(left, top, right, bottom) : rectToSvg(left, top, right, bottom, tl, tr, br, bl);
8920
+ return svgOf((sink) => outline(sink, geo));
8921
+ }
8922
+ buildPathData(geo) {
8923
+ return pathDataOf((sink) => outline(sink, geo));
8667
8924
  }
8668
8925
  supportsSpread() {
8669
8926
  return true;
@@ -8694,8 +8951,8 @@ var RectGeometry = class extends ShapeGeometry {
8694
8951
  tr.radius *= scale;
8695
8952
  br.radius *= scale;
8696
8953
  bl.radius *= scale;
8697
- if (tl.radius === 0 && tr.radius === 0 && br.radius === 0 && bl.radius === 0) return plainRect(left, top, right, bottom);
8698
- return rectToSvg(left, top, right, bottom, tl, tr, br, bl);
8954
+ if (tl.radius === 0 && tr.radius === 0 && br.radius === 0 && bl.radius === 0) return svgOf((sink) => plainRect(sink, left, top, right, bottom));
8955
+ return svgOf((sink) => roundedRect(sink, left, top, right, bottom, tl, tr, br, bl));
8699
8956
  }
8700
8957
  computeBounds(geo) {
8701
8958
  return {
@@ -8716,139 +8973,6 @@ function cornerScale(w, h, tl, tr, br, bl) {
8716
8973
  return Math.min(top, bottom, left, right);
8717
8974
  }
8718
8975
  //#endregion
8719
- //#region web/src/adapter/svg-path.ts
8720
- /** Endpoint-parameterised arc → cubic segments (SVG implementation notes F.6). */
8721
- function arcTo(b, x1, y1, rx, ry, phi, large, sweep, x2, y2) {
8722
- if (rx === 0 || ry === 0) {
8723
- b.lineTo(x2, y2);
8724
- return;
8725
- }
8726
- rx = Math.abs(rx);
8727
- ry = Math.abs(ry);
8728
- const c = Math.cos(phi), s = Math.sin(phi);
8729
- const dx = (x1 - x2) / 2, dy = (y1 - y2) / 2;
8730
- const xp = c * dx + s * dy, yp = -s * dx + c * dy;
8731
- const lam = xp * xp / (rx * rx) + yp * yp / (ry * ry);
8732
- if (lam > 1) {
8733
- rx *= Math.sqrt(lam);
8734
- ry *= Math.sqrt(lam);
8735
- }
8736
- const num = rx * rx * ry * ry - rx * rx * yp * yp - ry * ry * xp * xp;
8737
- const den = rx * rx * yp * yp + ry * ry * xp * xp;
8738
- let k = Math.sqrt(Math.max(0, num / den));
8739
- if (large === sweep) k = -k;
8740
- const cxp = k * rx * yp / ry, cyp = -k * ry * xp / rx;
8741
- const cx = c * cxp - s * cyp + (x1 + x2) / 2, cy = s * cxp + c * cyp + (y1 + y2) / 2;
8742
- const ang = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
8743
- const t1 = ang(1, 0, (xp - cxp) / rx, (yp - cyp) / ry);
8744
- let dt = ang((xp - cxp) / rx, (yp - cyp) / ry, (-xp - cxp) / rx, (-yp - cyp) / ry);
8745
- if (!sweep && dt > 0) dt -= 2 * Math.PI;
8746
- else if (sweep && dt < 0) dt += 2 * Math.PI;
8747
- const n = Math.ceil(Math.abs(dt) / (Math.PI / 2) - 1e-9);
8748
- const seg = dt / n;
8749
- const alpha = 4 / 3 * Math.tan(seg / 4);
8750
- const pt = (t) => [cx + rx * Math.cos(t) * c - ry * Math.sin(t) * s, cy + rx * Math.cos(t) * s + ry * Math.sin(t) * c];
8751
- const dpt = (t) => [-rx * Math.sin(t) * c - ry * Math.cos(t) * s, -rx * Math.sin(t) * s + ry * Math.cos(t) * c];
8752
- for (let i = 0; i < n; i++) {
8753
- const ta = t1 + i * seg, tb = ta + seg;
8754
- const [ax, ay] = pt(ta), [bx, by] = pt(tb), [dax, day] = dpt(ta), [dbx, dby] = dpt(tb);
8755
- b.cubicTo(ax + alpha * dax, ay + alpha * day, bx - alpha * dbx, by - alpha * dby, i === n - 1 ? x2 : bx, i === n - 1 ? y2 : by);
8756
- }
8757
- }
8758
- function parseSvgPath(d) {
8759
- const b = new PathBuilder();
8760
- const tokens = d.match(/[a-zA-Z]|-?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/g) ?? [];
8761
- let i = 0;
8762
- let cmd = "";
8763
- let x = 0, y = 0, sx = 0, sy = 0;
8764
- let lcx = 0, lcy = 0, lqx = 0, lqy = 0;
8765
- let prev = "";
8766
- const num = () => Number(tokens[i++]);
8767
- const more = () => i < tokens.length && !/[a-zA-Z]/.test(tokens[i]);
8768
- while (i < tokens.length) {
8769
- if (/[a-zA-Z]/.test(tokens[i])) cmd = tokens[i++];
8770
- const rel = cmd === cmd.toLowerCase();
8771
- const C = cmd.toUpperCase();
8772
- const ox = rel ? x : 0, oy = rel ? y : 0;
8773
- switch (C) {
8774
- case "M":
8775
- x = ox + num();
8776
- y = oy + num();
8777
- b.moveTo(x, y);
8778
- sx = x;
8779
- sy = y;
8780
- cmd = rel ? "l" : "L";
8781
- break;
8782
- case "L":
8783
- x = ox + num();
8784
- y = oy + num();
8785
- b.lineTo(x, y);
8786
- break;
8787
- case "H":
8788
- x = (rel ? x : 0) + num();
8789
- b.lineTo(x, y);
8790
- break;
8791
- case "V":
8792
- y = (rel ? y : 0) + num();
8793
- b.lineTo(x, y);
8794
- break;
8795
- case "C": {
8796
- const x1 = ox + num(), y1 = oy + num(), x2 = ox + num(), y2 = oy + num();
8797
- x = ox + num();
8798
- y = oy + num();
8799
- b.cubicTo(x1, y1, x2, y2, x, y);
8800
- lcx = x2;
8801
- lcy = y2;
8802
- break;
8803
- }
8804
- case "S": {
8805
- const r = prev === "C" || prev === "S";
8806
- const x1 = r ? 2 * x - lcx : x, y1 = r ? 2 * y - lcy : y;
8807
- const x2 = ox + num(), y2 = oy + num();
8808
- x = ox + num();
8809
- y = oy + num();
8810
- b.cubicTo(x1, y1, x2, y2, x, y);
8811
- lcx = x2;
8812
- lcy = y2;
8813
- break;
8814
- }
8815
- case "Q":
8816
- lqx = ox + num();
8817
- lqy = oy + num();
8818
- x = ox + num();
8819
- y = oy + num();
8820
- b.quadTo(lqx, lqy, x, y);
8821
- break;
8822
- case "T": {
8823
- const r = prev === "Q" || prev === "T";
8824
- lqx = r ? 2 * x - lqx : x;
8825
- lqy = r ? 2 * y - lqy : y;
8826
- x = ox + num();
8827
- y = oy + num();
8828
- b.quadTo(lqx, lqy, x, y);
8829
- break;
8830
- }
8831
- case "A": {
8832
- const rx = num(), ry = num(), phi = num() * Math.PI / 180, large = num() !== 0, sweep = num() !== 0;
8833
- const nx = ox + num(), ny = oy + num();
8834
- arcTo(b, x, y, rx, ry, phi, large, sweep, nx, ny);
8835
- x = nx;
8836
- y = ny;
8837
- break;
8838
- }
8839
- case "Z":
8840
- b.close();
8841
- x = sx;
8842
- y = sy;
8843
- break;
8844
- default: i++;
8845
- }
8846
- prev = C;
8847
- if (C === "Z" && more()) cmd = "L";
8848
- }
8849
- return b.build();
8850
- }
8851
- //#endregion
8852
8976
  //#region web/src/adapter/shapes.ts
8853
8977
  function flipPositionY(state, width, height) {
8854
8978
  const withPivotOffset = resolvePivotPosition(state, width, height);
@@ -8981,24 +9105,37 @@ function parse(svg, full) {
8981
9105
  function viaGeometry(Shape, state) {
8982
9106
  const shape = new Shape(state);
8983
9107
  const full = shape.fullState, geo = shape.geometry;
8984
- const base = parse(shape.buildSVGPath(geo), full);
8985
- const trims = shape.needsTrim();
8986
- const range = trims ? shape.getTrimRange() : null;
8987
- const fill = base && range ? trimPath(base, range.start, range.end) : base;
8988
- let stroke = fill;
8989
- const ring = shape instanceof EllipseGeometry ? shape.ringOutline(geo) : null;
8990
- if (ring != null) {
9108
+ const range = shape.needsTrim() ? shape.getTrimRange() : null;
9109
+ const base = once(() => {
9110
+ const outline = shape.buildPathData(geo);
9111
+ return outline && shapeTransform(full, outline);
9112
+ });
9113
+ const fill = once(() => {
9114
+ const p = base();
9115
+ return p && range ? trimPath(p, range.start, range.end) : p;
9116
+ });
9117
+ const stroke = once(() => {
9118
+ const ring = shape instanceof EllipseGeometry ? shape.ringOutline(geo) : null;
9119
+ if (ring == null) return fill();
8991
9120
  const outline = parse(ring, full);
8992
- stroke = outline && range ? trimPath(outline, range.start, range.end) : outline;
8993
- }
8994
- const interiorSvg = shape.buildSVGAlignInterior(geo);
8995
- const interior = interiorSvg != null ? parse(interiorSvg, full) : trims ? base : fill;
9121
+ return outline && range ? trimPath(outline, range.start, range.end) : outline;
9122
+ });
9123
+ const interior = once(() => {
9124
+ const svg = shape.buildSVGAlignInterior(geo);
9125
+ return svg != null ? parse(svg, full) : range ? base() : fill();
9126
+ });
8996
9127
  const b = shape.computeBounds(geo);
8997
9128
  const spread = shape.supportsSpread() ? (s) => parse(shape.buildSpreadSVGPath(geo, s), full) : void 0;
8998
9129
  return {
8999
- fill,
9000
- stroke,
9001
- interior,
9130
+ get fill() {
9131
+ return fill();
9132
+ },
9133
+ get stroke() {
9134
+ return stroke();
9135
+ },
9136
+ get interior() {
9137
+ return interior();
9138
+ },
9002
9139
  bounds: b ? [
9003
9140
  b.left,
9004
9141
  b.top,
@@ -9006,9 +9143,67 @@ function viaGeometry(Shape, state) {
9006
9143
  b.bottom
9007
9144
  ] : null,
9008
9145
  spread,
9009
- trimmed: !!range
9146
+ trimmed: !!range,
9147
+ native: range ? void 0 : nativeOf(shape, full, geo)
9148
+ };
9149
+ }
9150
+ function once(make) {
9151
+ let made = false;
9152
+ let value;
9153
+ return () => {
9154
+ if (!made) {
9155
+ value = make();
9156
+ made = true;
9157
+ }
9158
+ return value;
9010
9159
  };
9011
9160
  }
9161
+ /**
9162
+ * The shape the renderer draws itself: an unturned, unscaled rect whose corners
9163
+ * are all rounded (or square) and within half its shorter side, as the renderer
9164
+ * clamps them, or a whole ellipse with no hole.
9165
+ */
9166
+ function nativeOf(shape, full, geo) {
9167
+ if ((full.rotation ?? 0) !== 0 || (full.scale ?? 1) !== 1) return void 0;
9168
+ if (shape instanceof EllipseGeometry) {
9169
+ if (!geo.isFullShape || geo.ratio > 0) return void 0;
9170
+ const w = geo.halfWidth * 2, h = geo.halfHeight * 2;
9171
+ if (!(w > 0 && h > 0) || !Number.isFinite(w + h + geo.cx + geo.cy)) return void 0;
9172
+ return {
9173
+ kind: "ellipse",
9174
+ x: geo.cx - geo.halfWidth,
9175
+ y: geo.cy - geo.halfHeight,
9176
+ w,
9177
+ h,
9178
+ radii: [
9179
+ 0,
9180
+ 0,
9181
+ 0,
9182
+ 0
9183
+ ]
9184
+ };
9185
+ }
9186
+ if (shape instanceof RectGeometry) {
9187
+ const w = geo.right - geo.left, h = geo.bottom - geo.top;
9188
+ if (!(w > 0 && h > 0) || !Number.isFinite(w + h + geo.left + geo.top)) return void 0;
9189
+ const corners = [
9190
+ geo.tl,
9191
+ geo.tr,
9192
+ geo.br,
9193
+ geo.bl
9194
+ ];
9195
+ const limit = Math.min(w, h) / 2;
9196
+ if (corners.some((c) => !(c.radius >= 0 && c.radius <= limit) || c.radius > 0 && c.style === "angled")) return void 0;
9197
+ return {
9198
+ kind: "rect",
9199
+ x: geo.left,
9200
+ y: geo.top,
9201
+ w,
9202
+ h,
9203
+ radii: corners.map((c) => c.radius)
9204
+ };
9205
+ }
9206
+ }
9012
9207
  /** A path: data centred on its control-point bounds (or centerBounds), turned about the origin. */
9013
9208
  function pathShape(state) {
9014
9209
  const shape = new PathGeometry(state);
@@ -9411,6 +9606,29 @@ var SHAPE_OPS = new Set([
9411
9606
  "text",
9412
9607
  "richText"
9413
9608
  ]);
9609
+ var isSolid = (f) => f.type === "solid";
9610
+ /** `box` grown by `d` (shrunk when negative), each rounded corner with it; null where that is no longer the offset outline. */
9611
+ function offsetBox(box, d) {
9612
+ if (d === 0) return box;
9613
+ const w = box.w + 2 * d, h = box.h + 2 * d;
9614
+ if (!(w > 0 && h > 0)) return null;
9615
+ if (box.kind === "ellipse") return {
9616
+ ...box,
9617
+ x: box.x - d,
9618
+ y: box.y - d,
9619
+ w,
9620
+ h
9621
+ };
9622
+ if (box.radii.some((r) => r > 0 && !(r + d > 0))) return null;
9623
+ return {
9624
+ ...box,
9625
+ x: box.x - d,
9626
+ y: box.y - d,
9627
+ w,
9628
+ h,
9629
+ radii: box.radii.map((r) => r > 0 ? r + d : 0)
9630
+ };
9631
+ }
9414
9632
  var BLACK = {
9415
9633
  kind: "solid",
9416
9634
  color: [
@@ -9995,6 +10213,7 @@ var WgpuRenderContext = class extends CanvasRenderContext2D {
9995
10213
  paintFill(layers) {
9996
10214
  this.paintText(layers, null);
9997
10215
  if (!layers.length) return;
10216
+ if (this.fillNative(layers)) return;
9998
10217
  const path = this.surface("fill");
9999
10218
  const geoms = path ? [{ path }] : [];
10000
10219
  for (const s of this.shapes) if (s.members) geoms.push({ members: s.members });
@@ -10004,6 +10223,84 @@ var WgpuRenderContext = class extends CanvasRenderContext2D {
10004
10223
  this.shadows = [];
10005
10224
  for (const g of geoms) this.fillGeom(g, bounds, layers, all);
10006
10225
  }
10226
+ /**
10227
+ * One rect or ellipse painted solid, with no shadow to cast, drawn as the
10228
+ * renderer's own shape. Gradients and images stay paths: their paint space
10229
+ * is the path's.
10230
+ */
10231
+ fillNative(layers) {
10232
+ const shape = this.shapes.length === 1 ? this.shapes[0] : null;
10233
+ const box = shape?.native;
10234
+ if (!shape || !box || this.inVectorMask || this.shadows.length || !layers.every(isSolid)) return false;
10235
+ for (const f of layers) {
10236
+ const paint = fillPaint(f, shape.bounds, this.missing, this.assets);
10237
+ if (paint) this.native(this.cur, box, {
10238
+ fill: paint,
10239
+ opacity: this.worldAlpha * (f.opacity ?? 1),
10240
+ blend: this.blendOf(f)
10241
+ });
10242
+ }
10243
+ return true;
10244
+ }
10245
+ /**
10246
+ * An undashed solid stroke round one rect, or round a circle (an ellipse's
10247
+ * offset outline is no ellipse), drawn as the renderer's own shape. Biased by
10248
+ * `align`, the band is a centred stroke round the shape offset by
10249
+ * align·weight/2, which stays exact while no rounded corner shrinks away. The
10250
+ * renderer keeps a square corner sharp, which is a miter join.
10251
+ */
10252
+ strokeNative(st, weight, intWidth, dev) {
10253
+ const shape = this.shapes.length === 1 ? this.shapes[0] : null;
10254
+ const native = shape?.native;
10255
+ if (!shape || !native || weight <= 0 || (st.dash?.length ?? 0) > 0 || !st.fill.every(isSolid)) return false;
10256
+ if (native.kind === "ellipse" && Math.abs(native.w - native.h) > .001) return false;
10257
+ if (native.kind === "rect" && native.radii.some((r) => r === 0) && (st.join !== "miter" || !(st.miterLimit >= Math.SQRT2))) return false;
10258
+ const a = Math.max(-1, Math.min(1, st.align ?? 0));
10259
+ const box = offsetBox(native, a * weight / 2);
10260
+ if (!box) return false;
10261
+ const b = shape.bounds;
10262
+ const snapM = this.snapBounds([
10263
+ Math.fround(b[0]),
10264
+ Math.fround(b[1]),
10265
+ Math.fround(b[2]),
10266
+ Math.fround(b[3])
10267
+ ], intWidth, dev);
10268
+ const parent = snapM ? this.doc.group(this.cur, { transform: snapM }) : this.cur;
10269
+ for (const f of st.fill) {
10270
+ const paint = fillPaint(f, b, this.missing, this.assets);
10271
+ if (!paint) continue;
10272
+ const stroke = {
10273
+ paint,
10274
+ width: weight,
10275
+ join: st.join,
10276
+ cap: st.cap,
10277
+ miter: st.miterLimit
10278
+ };
10279
+ this.native(parent, box, {
10280
+ opacity: this.worldAlpha * (f.opacity ?? 1),
10281
+ stroke,
10282
+ ...a === 0 && { blend: this.blendOf(f) }
10283
+ });
10284
+ }
10285
+ return true;
10286
+ }
10287
+ native(parent, box, props) {
10288
+ const at = {
10289
+ ...props,
10290
+ transform: [
10291
+ 1,
10292
+ 0,
10293
+ 0,
10294
+ 1,
10295
+ box.x,
10296
+ box.y
10297
+ ],
10298
+ w: box.w,
10299
+ h: box.h,
10300
+ radii: box.radii
10301
+ };
10302
+ return box.kind === "rect" ? this.doc.rect(parent, at) : this.doc.ellipse(parent, at);
10303
+ }
10007
10304
  fillGeom(geom, bounds, layers, all) {
10008
10305
  if (this.inVectorMask) {
10009
10306
  this.leaf(this.cur, geom, { fill: BLACK });
@@ -10494,8 +10791,10 @@ var WgpuRenderContext = class extends CanvasRenderContext2D {
10494
10791
  /** StrokeHandler.snapPath: an axis-aligned thin stroke's shape scaled and
10495
10792
  * moved so its device bounds sit on pixel edges (centres for odd widths). */
10496
10793
  snap(path, intWidth, m) {
10794
+ return this.snapBounds(pathBounds(path), intWidth, m);
10795
+ }
10796
+ snapBounds([bx0, by0, bx1, by1], intWidth, m) {
10497
10797
  if (intWidth <= 0 || Math.abs(m[1]) > 1e-6 || Math.abs(m[2]) > 1e-6 || m[0] === 0 || m[3] === 0) return null;
10498
- const [bx0, by0, bx1, by1] = pathBounds(path);
10499
10798
  if (bx1 - bx0 <= 0 || by1 - by0 <= 0) return null;
10500
10799
  const sx = m[0], sy = m[3], tx = m[4], ty = m[5];
10501
10800
  const l = tx + sx * bx0, t = ty + sy * by0, r = tx + sx * bx1, b = ty + sy * by1;
@@ -10521,7 +10820,8 @@ var WgpuRenderContext = class extends CanvasRenderContext2D {
10521
10820
  }
10522
10821
  paintStroke(strokes) {
10523
10822
  for (const st of strokes) this.paintText(st.fill, st);
10524
- const fillAll = this.surface("fill");
10823
+ let fillAll;
10824
+ const surfaceFill = () => fillAll === void 0 ? fillAll = this.surface("fill") : fillAll;
10525
10825
  const dev = this.device();
10526
10826
  const scale = Math.sqrt(Math.abs(dev[0] * dev[3] - dev[1] * dev[2]));
10527
10827
  const rotated = isProjective(dev) || Math.abs(dev[1]) > 1e-6 || Math.abs(dev[2]) > 1e-6;
@@ -10531,6 +10831,7 @@ var WgpuRenderContext = class extends CanvasRenderContext2D {
10531
10831
  intWidth = Math.max(1, Math.round(weight * scale));
10532
10832
  weight = intWidth / scale;
10533
10833
  }
10834
+ if (this.strokeNative(st, weight, intWidth, dev)) continue;
10534
10835
  for (const shape of this.strokeShapes()) {
10535
10836
  let geom, interior;
10536
10837
  let plan = {
@@ -10548,7 +10849,7 @@ var WgpuRenderContext = class extends CanvasRenderContext2D {
10548
10849
  geom = { path: p.source ?? shape.stroke };
10549
10850
  interior = shape.alignInterior ? { members: shape.alignInterior } : shape.interior && isClosed(shape.interior) ? { path: shape.interior } : null;
10550
10851
  }
10551
- const bounds = this.bounds(fillAll ?? ("path" in geom ? geom.path : null));
10852
+ const bounds = this.bounds(surfaceFill() ?? ("path" in geom ? geom.path : null));
10552
10853
  const a = Math.max(-1, Math.min(1, st.align ?? 0));
10553
10854
  const halves = a !== 0 && interior !== null && !plan.ends.some((e) => isStrokeMarker(e.cap)) ? [[(1 + a) * weight, "out"], [(1 - a) * weight, "in"]] : [[weight, "both"]];
10554
10855
  const snapM = shape.fill ? this.snap(shape.fill, intWidth, dev) : null;
@@ -10587,7 +10888,7 @@ var WgpuRenderContext = class extends CanvasRenderContext2D {
10587
10888
  * whole, else their contours together (a union would close open ones),
10588
10889
  * aligned against the union. Text keeps each shape apart. */
10589
10890
  strokeShapes() {
10590
- if (this.shapes.some((s) => s.text)) return this.shapes;
10891
+ if (this.shapes.length < 2 || this.shapes.some((s) => s.text)) return this.shapes;
10591
10892
  const withPaths = this.shapes.filter((s) => s.members || s.fill && s.fill.verbs.length).sort((a, b) => (b.members ? 1 : 0) - (a.members ? 1 : 0));
10592
10893
  if (withPaths.length < 2) return this.shapes;
10593
10894
  const members = [];