sketchmark 1.2.0 → 1.2.1
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/README.md +7 -5
- package/dist/index.cjs +483 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +483 -10
- package/dist/index.js.map +1 -1
- package/dist/renderer/shapes/path-geometry.d.ts +8 -0
- package/dist/renderer/shapes/path-geometry.d.ts.map +1 -0
- package/dist/renderer/shapes/path.d.ts.map +1 -1
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +483 -10
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -351,7 +351,7 @@ Every node has the form:
|
|
|
351
351
|
| Image | `image` | URL-loaded image | label width |
|
|
352
352
|
| Icon | `icon` | Iconify icon | 48×48 + label |
|
|
353
353
|
| Line | `line` | Horizontal rule | label width |
|
|
354
|
-
| Path | `path` | Custom SVG path data | user-specified |
|
|
354
|
+
| Path | `path` | Custom SVG path data scaled into `width`/`height` | user-specified |
|
|
355
355
|
| Note | `note` | Sticky-note shape | line count × line height |
|
|
356
356
|
|
|
357
357
|
```
|
|
@@ -367,9 +367,11 @@ text myTxt label="Some prose" width=300
|
|
|
367
367
|
image myImg label="Logo" url="https://example.com/logo.png" width=120 height=60
|
|
368
368
|
icon myIcon label="Settings" name="mdi:cog"
|
|
369
369
|
line myLine label="Section" width=200
|
|
370
|
-
path myPath value="M 0 0 L 50 50 L 100 0 Z" width=100 height=60
|
|
371
|
-
note myNote label="Remember this!"
|
|
372
|
-
```
|
|
370
|
+
path myPath value="M 0 0 L 50 50 L 100 0 Z" width=100 height=60
|
|
371
|
+
note myNote label="Remember this!"
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
For `path`, write `value` in local coordinates near `0,0`. The renderer normalizes the path bounds into the node's `width` and `height`, and then uses `x`/`y` only for placement.
|
|
373
375
|
|
|
374
376
|
---
|
|
375
377
|
|
|
@@ -1051,7 +1053,7 @@ Nodes can also opt into authored absolute `x`/`y` positioning when their parent
|
|
|
1051
1053
|
| image (URL) | ✅ | Cross-origin images |
|
|
1052
1054
|
| icon (Iconify) | ✅ | Uses Iconify API |
|
|
1053
1055
|
| line | ✅ | Horizontal rule with label |
|
|
1054
|
-
| path (SVG path data) | ✅ |
|
|
1056
|
+
| path (SVG path data) | ✅ | Local SVG `d` attribute scaled into `width`/`height` |
|
|
1055
1057
|
| note | ✅ | Sticky-note shape |
|
|
1056
1058
|
| Multiline label (`\n`) | ✅ | Use `\n` in label strings |
|
|
1057
1059
|
| Per-node font override | ✅ | |
|
package/dist/index.cjs
CHANGED
|
@@ -4299,37 +4299,510 @@ const lineShape = {
|
|
|
4299
4299
|
},
|
|
4300
4300
|
};
|
|
4301
4301
|
|
|
4302
|
+
const COMMAND_RE = /^[AaCcHhLlMmQqSsTtVvZz]$/;
|
|
4303
|
+
const TOKEN_RE = /[AaCcHhLlMmQqSsTtVvZz]|[-+]?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?/g;
|
|
4304
|
+
const EPSILON = 1e-6;
|
|
4305
|
+
const PARAM_COUNTS = {
|
|
4306
|
+
A: 7,
|
|
4307
|
+
C: 6,
|
|
4308
|
+
H: 1,
|
|
4309
|
+
L: 2,
|
|
4310
|
+
M: 2,
|
|
4311
|
+
Q: 4,
|
|
4312
|
+
S: 4,
|
|
4313
|
+
T: 2,
|
|
4314
|
+
V: 1,
|
|
4315
|
+
Z: 0,
|
|
4316
|
+
};
|
|
4317
|
+
function isCommandToken(token) {
|
|
4318
|
+
return COMMAND_RE.test(token);
|
|
4319
|
+
}
|
|
4320
|
+
function formatNumber(value) {
|
|
4321
|
+
const rounded = Math.abs(value) < EPSILON ? 0 : Number(value.toFixed(3));
|
|
4322
|
+
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
4323
|
+
}
|
|
4324
|
+
function parseRawSegments(pathData) {
|
|
4325
|
+
const tokens = pathData.match(TOKEN_RE) ?? [];
|
|
4326
|
+
if (!tokens.length)
|
|
4327
|
+
return [];
|
|
4328
|
+
const segments = [];
|
|
4329
|
+
let index = 0;
|
|
4330
|
+
let currentCommand = null;
|
|
4331
|
+
while (index < tokens.length) {
|
|
4332
|
+
const token = tokens[index];
|
|
4333
|
+
if (isCommandToken(token)) {
|
|
4334
|
+
currentCommand = token;
|
|
4335
|
+
index += 1;
|
|
4336
|
+
if (token === "Z" || token === "z") {
|
|
4337
|
+
segments.push({ command: "Z", values: [] });
|
|
4338
|
+
}
|
|
4339
|
+
continue;
|
|
4340
|
+
}
|
|
4341
|
+
if (!currentCommand)
|
|
4342
|
+
break;
|
|
4343
|
+
const upper = currentCommand.toUpperCase();
|
|
4344
|
+
const paramCount = PARAM_COUNTS[upper];
|
|
4345
|
+
if (!paramCount) {
|
|
4346
|
+
index += 1;
|
|
4347
|
+
continue;
|
|
4348
|
+
}
|
|
4349
|
+
let isFirstMove = upper === "M";
|
|
4350
|
+
while (index < tokens.length && !isCommandToken(tokens[index])) {
|
|
4351
|
+
if (index + paramCount > tokens.length)
|
|
4352
|
+
return segments;
|
|
4353
|
+
const values = tokens
|
|
4354
|
+
.slice(index, index + paramCount)
|
|
4355
|
+
.map((value) => Number(value));
|
|
4356
|
+
if (values.some((value) => Number.isNaN(value))) {
|
|
4357
|
+
return segments;
|
|
4358
|
+
}
|
|
4359
|
+
if (upper === "M") {
|
|
4360
|
+
const moveCommand = isFirstMove
|
|
4361
|
+
? currentCommand
|
|
4362
|
+
: currentCommand === "m"
|
|
4363
|
+
? "l"
|
|
4364
|
+
: "L";
|
|
4365
|
+
segments.push({ command: moveCommand, values });
|
|
4366
|
+
isFirstMove = false;
|
|
4367
|
+
}
|
|
4368
|
+
else {
|
|
4369
|
+
segments.push({ command: currentCommand, values });
|
|
4370
|
+
}
|
|
4371
|
+
index += paramCount;
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
return segments;
|
|
4375
|
+
}
|
|
4376
|
+
function reflect(control, around) {
|
|
4377
|
+
return {
|
|
4378
|
+
x: around.x * 2 - control.x,
|
|
4379
|
+
y: around.y * 2 - control.y,
|
|
4380
|
+
};
|
|
4381
|
+
}
|
|
4382
|
+
function toAbsoluteSegments(rawSegments) {
|
|
4383
|
+
const segments = [];
|
|
4384
|
+
let current = { x: 0, y: 0 };
|
|
4385
|
+
let subpathStart = { x: 0, y: 0 };
|
|
4386
|
+
let previousCubicControl = null;
|
|
4387
|
+
let previousQuadraticControl = null;
|
|
4388
|
+
for (const segment of rawSegments) {
|
|
4389
|
+
const isRelative = segment.command === segment.command.toLowerCase();
|
|
4390
|
+
const command = segment.command.toUpperCase();
|
|
4391
|
+
const values = segment.values;
|
|
4392
|
+
switch (command) {
|
|
4393
|
+
case "M": {
|
|
4394
|
+
const x = isRelative ? current.x + values[0] : values[0];
|
|
4395
|
+
const y = isRelative ? current.y + values[1] : values[1];
|
|
4396
|
+
current = { x, y };
|
|
4397
|
+
subpathStart = { x, y };
|
|
4398
|
+
previousCubicControl = null;
|
|
4399
|
+
previousQuadraticControl = null;
|
|
4400
|
+
segments.push({ command: "M", values: [x, y] });
|
|
4401
|
+
break;
|
|
4402
|
+
}
|
|
4403
|
+
case "L": {
|
|
4404
|
+
const x = isRelative ? current.x + values[0] : values[0];
|
|
4405
|
+
const y = isRelative ? current.y + values[1] : values[1];
|
|
4406
|
+
current = { x, y };
|
|
4407
|
+
previousCubicControl = null;
|
|
4408
|
+
previousQuadraticControl = null;
|
|
4409
|
+
segments.push({ command: "L", values: [x, y] });
|
|
4410
|
+
break;
|
|
4411
|
+
}
|
|
4412
|
+
case "H": {
|
|
4413
|
+
const x = isRelative ? current.x + values[0] : values[0];
|
|
4414
|
+
current = { x, y: current.y };
|
|
4415
|
+
previousCubicControl = null;
|
|
4416
|
+
previousQuadraticControl = null;
|
|
4417
|
+
segments.push({ command: "L", values: [x, current.y] });
|
|
4418
|
+
break;
|
|
4419
|
+
}
|
|
4420
|
+
case "V": {
|
|
4421
|
+
const y = isRelative ? current.y + values[0] : values[0];
|
|
4422
|
+
current = { x: current.x, y };
|
|
4423
|
+
previousCubicControl = null;
|
|
4424
|
+
previousQuadraticControl = null;
|
|
4425
|
+
segments.push({ command: "L", values: [current.x, y] });
|
|
4426
|
+
break;
|
|
4427
|
+
}
|
|
4428
|
+
case "C": {
|
|
4429
|
+
const x1 = isRelative ? current.x + values[0] : values[0];
|
|
4430
|
+
const y1 = isRelative ? current.y + values[1] : values[1];
|
|
4431
|
+
const x2 = isRelative ? current.x + values[2] : values[2];
|
|
4432
|
+
const y2 = isRelative ? current.y + values[3] : values[3];
|
|
4433
|
+
const x = isRelative ? current.x + values[4] : values[4];
|
|
4434
|
+
const y = isRelative ? current.y + values[5] : values[5];
|
|
4435
|
+
current = { x, y };
|
|
4436
|
+
previousCubicControl = { x: x2, y: y2 };
|
|
4437
|
+
previousQuadraticControl = null;
|
|
4438
|
+
segments.push({ command: "C", values: [x1, y1, x2, y2, x, y] });
|
|
4439
|
+
break;
|
|
4440
|
+
}
|
|
4441
|
+
case "S": {
|
|
4442
|
+
const control1 = previousCubicControl
|
|
4443
|
+
? reflect(previousCubicControl, current)
|
|
4444
|
+
: { ...current };
|
|
4445
|
+
const x2 = isRelative ? current.x + values[0] : values[0];
|
|
4446
|
+
const y2 = isRelative ? current.y + values[1] : values[1];
|
|
4447
|
+
const x = isRelative ? current.x + values[2] : values[2];
|
|
4448
|
+
const y = isRelative ? current.y + values[3] : values[3];
|
|
4449
|
+
current = { x, y };
|
|
4450
|
+
previousCubicControl = { x: x2, y: y2 };
|
|
4451
|
+
previousQuadraticControl = null;
|
|
4452
|
+
segments.push({
|
|
4453
|
+
command: "C",
|
|
4454
|
+
values: [control1.x, control1.y, x2, y2, x, y],
|
|
4455
|
+
});
|
|
4456
|
+
break;
|
|
4457
|
+
}
|
|
4458
|
+
case "Q": {
|
|
4459
|
+
const x1 = isRelative ? current.x + values[0] : values[0];
|
|
4460
|
+
const y1 = isRelative ? current.y + values[1] : values[1];
|
|
4461
|
+
const x = isRelative ? current.x + values[2] : values[2];
|
|
4462
|
+
const y = isRelative ? current.y + values[3] : values[3];
|
|
4463
|
+
current = { x, y };
|
|
4464
|
+
previousCubicControl = null;
|
|
4465
|
+
previousQuadraticControl = { x: x1, y: y1 };
|
|
4466
|
+
segments.push({ command: "Q", values: [x1, y1, x, y] });
|
|
4467
|
+
break;
|
|
4468
|
+
}
|
|
4469
|
+
case "T": {
|
|
4470
|
+
const control = previousQuadraticControl
|
|
4471
|
+
? reflect(previousQuadraticControl, current)
|
|
4472
|
+
: { ...current };
|
|
4473
|
+
const x = isRelative ? current.x + values[0] : values[0];
|
|
4474
|
+
const y = isRelative ? current.y + values[1] : values[1];
|
|
4475
|
+
current = { x, y };
|
|
4476
|
+
previousCubicControl = null;
|
|
4477
|
+
previousQuadraticControl = control;
|
|
4478
|
+
segments.push({ command: "Q", values: [control.x, control.y, x, y] });
|
|
4479
|
+
break;
|
|
4480
|
+
}
|
|
4481
|
+
case "A": {
|
|
4482
|
+
const rx = Math.abs(values[0]);
|
|
4483
|
+
const ry = Math.abs(values[1]);
|
|
4484
|
+
const rotation = values[2];
|
|
4485
|
+
const largeArc = values[3];
|
|
4486
|
+
const sweep = values[4];
|
|
4487
|
+
const x = isRelative ? current.x + values[5] : values[5];
|
|
4488
|
+
const y = isRelative ? current.y + values[6] : values[6];
|
|
4489
|
+
current = { x, y };
|
|
4490
|
+
previousCubicControl = null;
|
|
4491
|
+
previousQuadraticControl = null;
|
|
4492
|
+
segments.push({
|
|
4493
|
+
command: "A",
|
|
4494
|
+
values: [rx, ry, rotation, largeArc, sweep, x, y],
|
|
4495
|
+
});
|
|
4496
|
+
break;
|
|
4497
|
+
}
|
|
4498
|
+
case "Z": {
|
|
4499
|
+
current = { ...subpathStart };
|
|
4500
|
+
previousCubicControl = null;
|
|
4501
|
+
previousQuadraticControl = null;
|
|
4502
|
+
segments.push({ command: "Z", values: [] });
|
|
4503
|
+
break;
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
return segments;
|
|
4508
|
+
}
|
|
4509
|
+
function cubicAt(p0, p1, p2, p3, t) {
|
|
4510
|
+
const mt = 1 - t;
|
|
4511
|
+
return (mt * mt * mt * p0 +
|
|
4512
|
+
3 * mt * mt * t * p1 +
|
|
4513
|
+
3 * mt * t * t * p2 +
|
|
4514
|
+
t * t * t * p3);
|
|
4515
|
+
}
|
|
4516
|
+
function quadraticAt(p0, p1, p2, t) {
|
|
4517
|
+
const mt = 1 - t;
|
|
4518
|
+
return mt * mt * p0 + 2 * mt * t * p1 + t * t * p2;
|
|
4519
|
+
}
|
|
4520
|
+
function cubicExtrema(p0, p1, p2, p3) {
|
|
4521
|
+
const a = -p0 + 3 * p1 - 3 * p2 + p3;
|
|
4522
|
+
const b = 3 * p0 - 6 * p1 + 3 * p2;
|
|
4523
|
+
const c = -3 * p0 + 3 * p1;
|
|
4524
|
+
if (Math.abs(a) < EPSILON) {
|
|
4525
|
+
if (Math.abs(b) < EPSILON)
|
|
4526
|
+
return [];
|
|
4527
|
+
return [-c / (2 * b)].filter((t) => t > 0 && t < 1);
|
|
4528
|
+
}
|
|
4529
|
+
const discriminant = 4 * b * b - 12 * a * c;
|
|
4530
|
+
if (discriminant < 0)
|
|
4531
|
+
return [];
|
|
4532
|
+
const sqrtDiscriminant = Math.sqrt(discriminant);
|
|
4533
|
+
return [
|
|
4534
|
+
(-2 * b + sqrtDiscriminant) / (6 * a),
|
|
4535
|
+
(-2 * b - sqrtDiscriminant) / (6 * a),
|
|
4536
|
+
].filter((t) => t > 0 && t < 1);
|
|
4537
|
+
}
|
|
4538
|
+
function quadraticExtrema(p0, p1, p2) {
|
|
4539
|
+
const denominator = p0 - 2 * p1 + p2;
|
|
4540
|
+
if (Math.abs(denominator) < EPSILON)
|
|
4541
|
+
return [];
|
|
4542
|
+
const t = (p0 - p1) / denominator;
|
|
4543
|
+
return t > 0 && t < 1 ? [t] : [];
|
|
4544
|
+
}
|
|
4545
|
+
function angleBetween(u, v) {
|
|
4546
|
+
const magnitude = Math.hypot(u.x, u.y) * Math.hypot(v.x, v.y);
|
|
4547
|
+
if (magnitude < EPSILON)
|
|
4548
|
+
return 0;
|
|
4549
|
+
const sign = u.x * v.y - u.y * v.x < 0 ? -1 : 1;
|
|
4550
|
+
const cosine = Math.min(1, Math.max(-1, (u.x * v.x + u.y * v.y) / magnitude));
|
|
4551
|
+
return sign * Math.acos(cosine);
|
|
4552
|
+
}
|
|
4553
|
+
function sampleArc(start, values) {
|
|
4554
|
+
let [rx, ry, rotation, largeArcFlag, sweepFlag, endX, endY] = values;
|
|
4555
|
+
if ((Math.abs(start.x - endX) < EPSILON && Math.abs(start.y - endY) < EPSILON) || rx < EPSILON || ry < EPSILON) {
|
|
4556
|
+
return [start, { x: endX, y: endY }];
|
|
4557
|
+
}
|
|
4558
|
+
rx = Math.abs(rx);
|
|
4559
|
+
ry = Math.abs(ry);
|
|
4560
|
+
const phi = (rotation * Math.PI) / 180;
|
|
4561
|
+
const cosPhi = Math.cos(phi);
|
|
4562
|
+
const sinPhi = Math.sin(phi);
|
|
4563
|
+
const dx2 = (start.x - endX) / 2;
|
|
4564
|
+
const dy2 = (start.y - endY) / 2;
|
|
4565
|
+
const x1p = cosPhi * dx2 + sinPhi * dy2;
|
|
4566
|
+
const y1p = -sinPhi * dx2 + cosPhi * dy2;
|
|
4567
|
+
const lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
|
|
4568
|
+
if (lambda > 1) {
|
|
4569
|
+
const scale = Math.sqrt(lambda);
|
|
4570
|
+
rx *= scale;
|
|
4571
|
+
ry *= scale;
|
|
4572
|
+
}
|
|
4573
|
+
const rx2 = rx * rx;
|
|
4574
|
+
const ry2 = ry * ry;
|
|
4575
|
+
const x1p2 = x1p * x1p;
|
|
4576
|
+
const y1p2 = y1p * y1p;
|
|
4577
|
+
const numerator = rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2;
|
|
4578
|
+
const denominator = rx2 * y1p2 + ry2 * x1p2;
|
|
4579
|
+
const factor = denominator < EPSILON ? 0 : Math.sqrt(Math.max(0, numerator / denominator));
|
|
4580
|
+
const sign = largeArcFlag === sweepFlag ? -1 : 1;
|
|
4581
|
+
const cxp = sign * factor * ((rx * y1p) / ry);
|
|
4582
|
+
const cyp = sign * factor * (-(ry * x1p) / rx);
|
|
4583
|
+
const cx = cosPhi * cxp - sinPhi * cyp + (start.x + endX) / 2;
|
|
4584
|
+
const cy = sinPhi * cxp + cosPhi * cyp + (start.y + endY) / 2;
|
|
4585
|
+
const startVector = {
|
|
4586
|
+
x: (x1p - cxp) / rx,
|
|
4587
|
+
y: (y1p - cyp) / ry,
|
|
4588
|
+
};
|
|
4589
|
+
const endVector = {
|
|
4590
|
+
x: (-x1p - cxp) / rx,
|
|
4591
|
+
y: (-y1p - cyp) / ry,
|
|
4592
|
+
};
|
|
4593
|
+
let deltaTheta = angleBetween(startVector, endVector);
|
|
4594
|
+
if (!sweepFlag && deltaTheta > 0)
|
|
4595
|
+
deltaTheta -= Math.PI * 2;
|
|
4596
|
+
if (sweepFlag && deltaTheta < 0)
|
|
4597
|
+
deltaTheta += Math.PI * 2;
|
|
4598
|
+
const theta1 = angleBetween({ x: 1, y: 0 }, startVector);
|
|
4599
|
+
const steps = Math.max(12, Math.ceil(Math.abs(deltaTheta) / (Math.PI / 8)));
|
|
4600
|
+
const points = [];
|
|
4601
|
+
for (let index = 0; index <= steps; index += 1) {
|
|
4602
|
+
const theta = theta1 + (deltaTheta * index) / steps;
|
|
4603
|
+
const cosTheta = Math.cos(theta);
|
|
4604
|
+
const sinTheta = Math.sin(theta);
|
|
4605
|
+
points.push({
|
|
4606
|
+
x: cx + rx * cosPhi * cosTheta - ry * sinPhi * sinTheta,
|
|
4607
|
+
y: cy + rx * sinPhi * cosTheta + ry * cosPhi * sinTheta,
|
|
4608
|
+
});
|
|
4609
|
+
}
|
|
4610
|
+
return points;
|
|
4611
|
+
}
|
|
4612
|
+
function boundsFromAbsoluteSegments(segments) {
|
|
4613
|
+
if (!segments.length)
|
|
4614
|
+
return null;
|
|
4615
|
+
let minX = Infinity;
|
|
4616
|
+
let minY = Infinity;
|
|
4617
|
+
let maxX = -Infinity;
|
|
4618
|
+
let maxY = -Infinity;
|
|
4619
|
+
const include = (point) => {
|
|
4620
|
+
minX = Math.min(minX, point.x);
|
|
4621
|
+
minY = Math.min(minY, point.y);
|
|
4622
|
+
maxX = Math.max(maxX, point.x);
|
|
4623
|
+
maxY = Math.max(maxY, point.y);
|
|
4624
|
+
};
|
|
4625
|
+
let current = { x: 0, y: 0 };
|
|
4626
|
+
let subpathStart = { x: 0, y: 0 };
|
|
4627
|
+
for (const segment of segments) {
|
|
4628
|
+
switch (segment.command) {
|
|
4629
|
+
case "M": {
|
|
4630
|
+
current = { x: segment.values[0], y: segment.values[1] };
|
|
4631
|
+
subpathStart = { ...current };
|
|
4632
|
+
include(current);
|
|
4633
|
+
break;
|
|
4634
|
+
}
|
|
4635
|
+
case "L": {
|
|
4636
|
+
include(current);
|
|
4637
|
+
current = { x: segment.values[0], y: segment.values[1] };
|
|
4638
|
+
include(current);
|
|
4639
|
+
break;
|
|
4640
|
+
}
|
|
4641
|
+
case "C": {
|
|
4642
|
+
const [x1, y1, x2, y2, x, y] = segment.values;
|
|
4643
|
+
const ts = new Set([0, 1]);
|
|
4644
|
+
cubicExtrema(current.x, x1, x2, x).forEach((value) => ts.add(value));
|
|
4645
|
+
cubicExtrema(current.y, y1, y2, y).forEach((value) => ts.add(value));
|
|
4646
|
+
for (const t of ts) {
|
|
4647
|
+
include({
|
|
4648
|
+
x: cubicAt(current.x, x1, x2, x, t),
|
|
4649
|
+
y: cubicAt(current.y, y1, y2, y, t),
|
|
4650
|
+
});
|
|
4651
|
+
}
|
|
4652
|
+
current = { x, y };
|
|
4653
|
+
break;
|
|
4654
|
+
}
|
|
4655
|
+
case "Q": {
|
|
4656
|
+
const [x1, y1, x, y] = segment.values;
|
|
4657
|
+
const ts = new Set([0, 1]);
|
|
4658
|
+
quadraticExtrema(current.x, x1, x).forEach((value) => ts.add(value));
|
|
4659
|
+
quadraticExtrema(current.y, y1, y).forEach((value) => ts.add(value));
|
|
4660
|
+
for (const t of ts) {
|
|
4661
|
+
include({
|
|
4662
|
+
x: quadraticAt(current.x, x1, x, t),
|
|
4663
|
+
y: quadraticAt(current.y, y1, y, t),
|
|
4664
|
+
});
|
|
4665
|
+
}
|
|
4666
|
+
current = { x, y };
|
|
4667
|
+
break;
|
|
4668
|
+
}
|
|
4669
|
+
case "A": {
|
|
4670
|
+
for (const point of sampleArc(current, segment.values)) {
|
|
4671
|
+
include(point);
|
|
4672
|
+
}
|
|
4673
|
+
current = { x: segment.values[5], y: segment.values[6] };
|
|
4674
|
+
break;
|
|
4675
|
+
}
|
|
4676
|
+
case "Z": {
|
|
4677
|
+
include(current);
|
|
4678
|
+
include(subpathStart);
|
|
4679
|
+
current = { ...subpathStart };
|
|
4680
|
+
break;
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(maxX) || !Number.isFinite(maxY)) {
|
|
4685
|
+
return null;
|
|
4686
|
+
}
|
|
4687
|
+
return { minX, minY, maxX, maxY };
|
|
4688
|
+
}
|
|
4689
|
+
function transformX(x, bounds, scaleX) {
|
|
4690
|
+
return (x - bounds.minX) * scaleX;
|
|
4691
|
+
}
|
|
4692
|
+
function transformY(y, bounds, scaleY) {
|
|
4693
|
+
return (y - bounds.minY) * scaleY;
|
|
4694
|
+
}
|
|
4695
|
+
function buildScaledPathData(segments, bounds, width, height) {
|
|
4696
|
+
const sourceWidth = Math.max(bounds.maxX - bounds.minX, EPSILON);
|
|
4697
|
+
const sourceHeight = Math.max(bounds.maxY - bounds.minY, EPSILON);
|
|
4698
|
+
const scaleX = width / sourceWidth;
|
|
4699
|
+
const scaleY = height / sourceHeight;
|
|
4700
|
+
return segments
|
|
4701
|
+
.map((segment) => {
|
|
4702
|
+
switch (segment.command) {
|
|
4703
|
+
case "M":
|
|
4704
|
+
case "L":
|
|
4705
|
+
return [
|
|
4706
|
+
segment.command,
|
|
4707
|
+
formatNumber(transformX(segment.values[0], bounds, scaleX)),
|
|
4708
|
+
formatNumber(transformY(segment.values[1], bounds, scaleY)),
|
|
4709
|
+
].join(" ");
|
|
4710
|
+
case "C":
|
|
4711
|
+
return [
|
|
4712
|
+
"C",
|
|
4713
|
+
formatNumber(transformX(segment.values[0], bounds, scaleX)),
|
|
4714
|
+
formatNumber(transformY(segment.values[1], bounds, scaleY)),
|
|
4715
|
+
formatNumber(transformX(segment.values[2], bounds, scaleX)),
|
|
4716
|
+
formatNumber(transformY(segment.values[3], bounds, scaleY)),
|
|
4717
|
+
formatNumber(transformX(segment.values[4], bounds, scaleX)),
|
|
4718
|
+
formatNumber(transformY(segment.values[5], bounds, scaleY)),
|
|
4719
|
+
].join(" ");
|
|
4720
|
+
case "Q":
|
|
4721
|
+
return [
|
|
4722
|
+
"Q",
|
|
4723
|
+
formatNumber(transformX(segment.values[0], bounds, scaleX)),
|
|
4724
|
+
formatNumber(transformY(segment.values[1], bounds, scaleY)),
|
|
4725
|
+
formatNumber(transformX(segment.values[2], bounds, scaleX)),
|
|
4726
|
+
formatNumber(transformY(segment.values[3], bounds, scaleY)),
|
|
4727
|
+
].join(" ");
|
|
4728
|
+
case "A":
|
|
4729
|
+
return [
|
|
4730
|
+
"A",
|
|
4731
|
+
formatNumber(segment.values[0] * scaleX),
|
|
4732
|
+
formatNumber(segment.values[1] * scaleY),
|
|
4733
|
+
formatNumber(segment.values[2]),
|
|
4734
|
+
formatNumber(segment.values[3]),
|
|
4735
|
+
formatNumber(segment.values[4]),
|
|
4736
|
+
formatNumber(transformX(segment.values[5], bounds, scaleX)),
|
|
4737
|
+
formatNumber(transformY(segment.values[6], bounds, scaleY)),
|
|
4738
|
+
].join(" ");
|
|
4739
|
+
case "Z":
|
|
4740
|
+
return "Z";
|
|
4741
|
+
}
|
|
4742
|
+
})
|
|
4743
|
+
.join(" ");
|
|
4744
|
+
}
|
|
4745
|
+
function intrinsicSizeFromBounds(bounds) {
|
|
4746
|
+
if (!bounds)
|
|
4747
|
+
return { width: 100, height: 100 };
|
|
4748
|
+
return {
|
|
4749
|
+
width: Math.max(1, Math.ceil(bounds.maxX - bounds.minX)),
|
|
4750
|
+
height: Math.max(1, Math.ceil(bounds.maxY - bounds.minY)),
|
|
4751
|
+
};
|
|
4752
|
+
}
|
|
4753
|
+
function parsePathGeometry(pathData) {
|
|
4754
|
+
const segments = toAbsoluteSegments(parseRawSegments(pathData));
|
|
4755
|
+
return {
|
|
4756
|
+
segments,
|
|
4757
|
+
bounds: boundsFromAbsoluteSegments(segments),
|
|
4758
|
+
};
|
|
4759
|
+
}
|
|
4760
|
+
function getPathIntrinsicSize(pathData) {
|
|
4761
|
+
if (!pathData)
|
|
4762
|
+
return { width: 100, height: 100 };
|
|
4763
|
+
return intrinsicSizeFromBounds(parsePathGeometry(pathData).bounds);
|
|
4764
|
+
}
|
|
4765
|
+
function getRenderablePathData(pathData, width, height) {
|
|
4766
|
+
if (!pathData)
|
|
4767
|
+
return null;
|
|
4768
|
+
const { segments, bounds } = parsePathGeometry(pathData);
|
|
4769
|
+
if (!segments.length || !bounds)
|
|
4770
|
+
return pathData;
|
|
4771
|
+
return buildScaledPathData(segments, bounds, Math.max(1, width), Math.max(1, height));
|
|
4772
|
+
}
|
|
4773
|
+
function getRenderableNodePathData(node) {
|
|
4774
|
+
return getRenderablePathData(node.pathData, node.w, node.h);
|
|
4775
|
+
}
|
|
4776
|
+
|
|
4302
4777
|
const pathShape = {
|
|
4303
4778
|
size(n, labelW) {
|
|
4304
|
-
|
|
4305
|
-
const w = n.width ?? Math.max(
|
|
4779
|
+
const intrinsic = getPathIntrinsicSize(n.pathData);
|
|
4780
|
+
const w = n.width ?? Math.max(intrinsic.width, Math.min(300, labelW + 20));
|
|
4306
4781
|
n.w = w;
|
|
4307
4782
|
if (!n.h) {
|
|
4308
|
-
if (!n.width && labelW + 20 > w) {
|
|
4783
|
+
if (!n.width && !n.height && labelW + 20 > w) {
|
|
4309
4784
|
const fontSize = Number(n.style?.fontSize ?? 14);
|
|
4310
4785
|
const lines = Math.ceil(labelW / (w - 20));
|
|
4311
|
-
n.h = Math.max(
|
|
4786
|
+
n.h = Math.max(intrinsic.height, lines * fontSize * 1.5 + 20);
|
|
4312
4787
|
}
|
|
4313
4788
|
else {
|
|
4314
|
-
n.h = n.height ??
|
|
4789
|
+
n.h = n.height ?? intrinsic.height;
|
|
4315
4790
|
}
|
|
4316
4791
|
}
|
|
4317
4792
|
},
|
|
4318
4793
|
renderSVG(rc, n, _palette, opts) {
|
|
4319
|
-
const d = n
|
|
4794
|
+
const d = getRenderableNodePathData(n);
|
|
4320
4795
|
if (!d) {
|
|
4321
|
-
// No path data — render placeholder box
|
|
4322
4796
|
return [rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts)];
|
|
4323
4797
|
}
|
|
4324
4798
|
const el = rc.path(d, opts);
|
|
4325
|
-
// Wrap in a group to translate the user's path to the node position
|
|
4326
4799
|
const g = document.createElementNS(SVG_NS, "g");
|
|
4327
4800
|
g.setAttribute("transform", `translate(${n.x},${n.y})`);
|
|
4328
4801
|
g.appendChild(el);
|
|
4329
4802
|
return [g];
|
|
4330
4803
|
},
|
|
4331
4804
|
renderCanvas(rc, ctx, n, _palette, opts) {
|
|
4332
|
-
const d = n
|
|
4805
|
+
const d = getRenderableNodePathData(n);
|
|
4333
4806
|
if (!d) {
|
|
4334
4807
|
rc.rectangle(n.x + 1, n.y + 1, n.w - 2, n.h - 2, opts);
|
|
4335
4808
|
return;
|
|
@@ -7941,7 +8414,7 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
7941
8414
|
ng.dataset.w = String(n.w);
|
|
7942
8415
|
ng.dataset.h = String(n.h);
|
|
7943
8416
|
if (n.pathData)
|
|
7944
|
-
ng.dataset.pathData = n.pathData;
|
|
8417
|
+
ng.dataset.pathData = getRenderableNodePathData(n) ?? n.pathData;
|
|
7945
8418
|
if (n.meta?.animationParent)
|
|
7946
8419
|
ng.dataset.animationParent = n.meta.animationParent;
|
|
7947
8420
|
if (n.style?.opacity != null)
|