@motionscript/renderer 0.1.0-alpha.9 → 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/CHANGELOG.md +14 -0
- package/dist/browser/index.js +10 -10
- package/dist/browser/index.js.map +4 -4
- package/dist/browser/manifest.json +1 -1
- package/dist/index.js +510 -209
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -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
|
-
*
|
|
8291
|
-
*
|
|
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
|
-
*
|
|
8514
|
+
* Nothing for a zero radius (the edge runs on to a sharp apex).
|
|
8294
8515
|
*/
|
|
8295
|
-
function
|
|
8296
|
-
if (c.radius <= 0) return
|
|
8297
|
-
|
|
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
|
-
*
|
|
8301
|
-
*
|
|
8302
|
-
*
|
|
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
|
|
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
|
|
8307
|
-
|
|
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
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
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
|
|
8372
|
-
const
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
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
|
-
|
|
8385
|
-
|
|
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 {
|
|
8390
|
-
|
|
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 {
|
|
8394
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
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
|
-
|
|
8666
|
-
|
|
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
|
|
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
|
|
8985
|
-
const
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
const
|
|
8990
|
-
|
|
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
|
-
|
|
8993
|
-
}
|
|
8994
|
-
const
|
|
8995
|
-
|
|
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
|
-
|
|
9001
|
-
|
|
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
|
-
|
|
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(
|
|
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 = [];
|