vizo-mcp 0.1.0 → 0.3.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/README.md +15 -2
- package/dist/fonts/CascadiaCode-Regular.ttf +0 -0
- package/dist/fonts/CascadiaCode-Regular.woff2 +0 -0
- package/dist/fonts/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.ttf +0 -0
- package/dist/fonts/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2 +0 -0
- package/dist/fonts/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.ttf +0 -0
- package/dist/fonts/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2 +0 -0
- package/dist/fonts/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.ttf +0 -0
- package/dist/fonts/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2 +0 -0
- package/dist/fonts/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.ttf +0 -0
- package/dist/fonts/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2 +0 -0
- package/dist/fonts/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.ttf +0 -0
- package/dist/fonts/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2 +0 -0
- package/dist/fonts/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.ttf +0 -0
- package/dist/fonts/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2 +0 -0
- package/dist/fonts/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.ttf +0 -0
- package/dist/fonts/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2 +0 -0
- package/dist/fonts/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.ttf +0 -0
- package/dist/fonts/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2 +0 -0
- package/dist/fonts/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.ttf +0 -0
- package/dist/fonts/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2 +0 -0
- package/dist/fonts/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.ttf +0 -0
- package/dist/fonts/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2 +0 -0
- package/dist/fonts/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.ttf +0 -0
- package/dist/fonts/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2 +0 -0
- package/dist/fonts/LiberationSans-Regular.ttf +0 -0
- package/dist/fonts/LiberationSans-Regular.woff2 +0 -0
- package/dist/fonts/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.ttf +0 -0
- package/dist/fonts/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 +0 -0
- package/dist/fonts/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.ttf +0 -0
- package/dist/fonts/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 +0 -0
- package/dist/fonts/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.ttf +0 -0
- package/dist/fonts/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2 +0 -0
- package/dist/fonts/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.ttf +0 -0
- package/dist/fonts/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2 +0 -0
- package/dist/fonts/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.ttf +0 -0
- package/dist/fonts/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2 +0 -0
- package/dist/fonts/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.ttf +0 -0
- package/dist/fonts/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2 +0 -0
- package/dist/fonts/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.ttf +0 -0
- package/dist/fonts/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2 +0 -0
- package/dist/fonts/Virgil-Regular.ttf +0 -0
- package/dist/fonts/Virgil-Regular.woff2 +0 -0
- package/dist/fonts/fonts.json +146 -0
- package/dist/index.js +2986 -164
- package/fonts/README.md +15 -0
- package/package.json +13 -9
package/dist/index.js
CHANGED
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
ListToolsRequestSchema,
|
|
16
16
|
ReadResourceRequestSchema
|
|
17
17
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
18
|
+
import { readFileSync as readFileSync3, statSync } from "node:fs";
|
|
19
|
+
import { resolve as resolve2 } from "node:path";
|
|
18
20
|
|
|
19
21
|
// ../diagram/src/schema.ts
|
|
20
22
|
import { z } from "zod";
|
|
@@ -41,11 +43,16 @@ var Group = z.object({
|
|
|
41
43
|
children: z.array(id).describe("Node ids (or nested group ids) inside this group"),
|
|
42
44
|
color: Color.optional()
|
|
43
45
|
});
|
|
46
|
+
var nodeImage = {
|
|
47
|
+
image: z.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/, "image must be a fileId").optional().describe("fileId of an image on this board (from add_image, or an image's fileId in get_board). The node becomes a card: picture above the label."),
|
|
48
|
+
imageSize: z.number().min(16).max(800).optional().describe("Longer side of the picture in board units (default 96)")
|
|
49
|
+
};
|
|
44
50
|
var FlowchartNode = z.object({
|
|
45
51
|
id,
|
|
46
52
|
label,
|
|
47
|
-
shape: z.enum(["process", "decision", "terminal", "io", "database", "subroutine", "note"]).optional().describe("process (default) rectangle; decision diamond; terminal start/end pill; database; note sticky"),
|
|
48
|
-
color: Color.optional()
|
|
53
|
+
shape: z.enum(["process", "decision", "terminal", "io", "database", "subroutine", "note"]).optional().describe("process (default) rectangle; decision diamond; terminal start/end pill; database; note sticky. Nodes with an image are always rectangles."),
|
|
54
|
+
color: Color.optional(),
|
|
55
|
+
...nodeImage
|
|
49
56
|
});
|
|
50
57
|
var FlowchartSpec = z.object({
|
|
51
58
|
kind: z.literal("flowchart"),
|
|
@@ -61,7 +68,8 @@ var ComponentNode = z.object({
|
|
|
61
68
|
type: z.enum(["service", "database", "queue", "cache", "client", "external", "storage", "function", "user", "generic"]).optional().describe("Drives the shape and color"),
|
|
62
69
|
tech: z.string().max(60).optional().describe("Technology caption, e.g. 'PostgreSQL', 'React'"),
|
|
63
70
|
description: z.string().max(200).optional(),
|
|
64
|
-
color: Color.optional()
|
|
71
|
+
color: Color.optional(),
|
|
72
|
+
...nodeImage
|
|
65
73
|
});
|
|
66
74
|
var ComponentSpec = z.object({
|
|
67
75
|
kind: z.literal("component"),
|
|
@@ -159,6 +167,10 @@ var DiagramSpec = z.discriminatedUnion("kind", [
|
|
|
159
167
|
StateSpec,
|
|
160
168
|
MindmapSpec
|
|
161
169
|
]);
|
|
170
|
+
function specImageIds(spec) {
|
|
171
|
+
const nodes = spec.kind === "flowchart" || spec.kind === "component" ? spec.nodes : [];
|
|
172
|
+
return [...new Set(nodes.flatMap((n) => n.image ? [n.image] : []))];
|
|
173
|
+
}
|
|
162
174
|
function validateSpec(spec) {
|
|
163
175
|
const errors = [];
|
|
164
176
|
const check = (ids, refs, what, groups) => {
|
|
@@ -245,7 +257,7 @@ function measureLine(text2, fontSize, font) {
|
|
|
245
257
|
}
|
|
246
258
|
function measureText(text2, fontSize, font) {
|
|
247
259
|
const lines = text2.split("\n");
|
|
248
|
-
const width = Math.max(0, ...lines.map((
|
|
260
|
+
const width = Math.max(0, ...lines.map((l2) => measureLine(l2, fontSize, font)));
|
|
249
261
|
const height = lines.length * fontSize * LINE_HEIGHT[font];
|
|
250
262
|
return { width: Math.ceil(width), height: Math.ceil(height), lines: lines.length };
|
|
251
263
|
}
|
|
@@ -253,17 +265,17 @@ function wrapText(text2, fontSize, font, maxWidth) {
|
|
|
253
265
|
const out = [];
|
|
254
266
|
for (const para of text2.split("\n")) {
|
|
255
267
|
const words = para.split(/\s+/).filter(Boolean);
|
|
256
|
-
let
|
|
268
|
+
let line2 = "";
|
|
257
269
|
for (const word of words) {
|
|
258
|
-
const candidate =
|
|
259
|
-
if (!
|
|
260
|
-
|
|
270
|
+
const candidate = line2 ? `${line2} ${word}` : word;
|
|
271
|
+
if (!line2 || measureLine(candidate, fontSize, font) <= maxWidth) {
|
|
272
|
+
line2 = candidate;
|
|
261
273
|
} else {
|
|
262
|
-
out.push(
|
|
263
|
-
|
|
274
|
+
out.push(line2);
|
|
275
|
+
line2 = word;
|
|
264
276
|
}
|
|
265
277
|
}
|
|
266
|
-
out.push(
|
|
278
|
+
out.push(line2);
|
|
267
279
|
}
|
|
268
280
|
return out.join("\n");
|
|
269
281
|
}
|
|
@@ -302,7 +314,7 @@ var randomNonce = () => Math.floor(Math.random() * 2 ** 31);
|
|
|
302
314
|
function randomId(len = 20) {
|
|
303
315
|
const abc = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
304
316
|
const bytes = crypto.getRandomValues(new Uint8Array(len));
|
|
305
|
-
return Array.from(bytes, (
|
|
317
|
+
return Array.from(bytes, (b2) => abc[b2 % abc.length]).join("");
|
|
306
318
|
}
|
|
307
319
|
function base(id2, type, x, y, width, height, o = {}) {
|
|
308
320
|
return {
|
|
@@ -401,23 +413,23 @@ function arrow(id2, pts, o = {}) {
|
|
|
401
413
|
const bind = (target, p) => {
|
|
402
414
|
if (!target) return null;
|
|
403
415
|
target.boundElements = [...target.boundElements ?? [], { id: id2, type: "arrow" }];
|
|
404
|
-
const
|
|
416
|
+
const b2 = { elementId: target.id, focus: 0, gap: 4 };
|
|
405
417
|
if (o.elbowed) {
|
|
406
|
-
|
|
418
|
+
b2.fixedPoint = [
|
|
407
419
|
clamp((p[0] - target.x) / (target.width || 1), 0, 1),
|
|
408
420
|
clamp((p[1] - target.y) / (target.height || 1), 0, 1)
|
|
409
421
|
];
|
|
410
|
-
|
|
422
|
+
b2.gap = 0;
|
|
411
423
|
}
|
|
412
|
-
return
|
|
424
|
+
return b2;
|
|
413
425
|
};
|
|
414
426
|
el.startBinding = bind(o.start, clean[0]);
|
|
415
427
|
el.endBinding = bind(o.end, clean[clean.length - 1]);
|
|
416
428
|
return el;
|
|
417
429
|
}
|
|
418
|
-
function arrowLabel(
|
|
419
|
-
const t = text(`${
|
|
420
|
-
const pts =
|
|
430
|
+
function arrowLabel(a2, value, o = {}) {
|
|
431
|
+
const t = text(`${a2.id}.t`, 0, 0, value, { textAlign: "center", verticalAlign: "middle", fontSize: 16, ...o });
|
|
432
|
+
const pts = a2.points;
|
|
421
433
|
let mid;
|
|
422
434
|
if (pts.length % 2 === 1) mid = pts[Math.floor(pts.length / 2)];
|
|
423
435
|
else {
|
|
@@ -425,14 +437,23 @@ function arrowLabel(a, value, o = {}) {
|
|
|
425
437
|
const q = pts[pts.length / 2];
|
|
426
438
|
mid = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
|
|
427
439
|
}
|
|
428
|
-
t.containerId =
|
|
429
|
-
t.x = round(
|
|
430
|
-
t.y = round(
|
|
431
|
-
t.groupIds =
|
|
432
|
-
if (
|
|
433
|
-
|
|
440
|
+
t.containerId = a2.id;
|
|
441
|
+
t.x = round(a2.x + mid[0] - t.width / 2);
|
|
442
|
+
t.y = round(a2.y + mid[1] - t.height / 2);
|
|
443
|
+
t.groupIds = a2.groupIds ?? [];
|
|
444
|
+
if (a2.customData) t.customData = { ...a2.customData, role: "label" };
|
|
445
|
+
a2.boundElements = [...a2.boundElements ?? [], { id: t.id, type: "text" }];
|
|
434
446
|
return t;
|
|
435
447
|
}
|
|
448
|
+
function image(id2, x, y, w, h, fileId, o = {}) {
|
|
449
|
+
return {
|
|
450
|
+
...base(id2, "image", x, y, w, h, { strokeColor: "transparent", ...o }),
|
|
451
|
+
fileId,
|
|
452
|
+
status: "saved",
|
|
453
|
+
scale: [1, 1],
|
|
454
|
+
crop: null
|
|
455
|
+
};
|
|
456
|
+
}
|
|
436
457
|
function frame(id2, x, y, w, h, name, o = {}) {
|
|
437
458
|
return { ...base(id2, "frame", x, y, w, h, { roughness: 0, strokeColor: "#bbb", ...o }), roundness: null, name };
|
|
438
459
|
}
|
|
@@ -445,18 +466,18 @@ function dedupe(pts) {
|
|
|
445
466
|
}
|
|
446
467
|
const res = [];
|
|
447
468
|
for (let i = 0; i < out.length; i++) {
|
|
448
|
-
const
|
|
449
|
-
const
|
|
469
|
+
const a2 = res[res.length - 1];
|
|
470
|
+
const b2 = out[i];
|
|
450
471
|
const c = out[i + 1];
|
|
451
|
-
if (
|
|
472
|
+
if (a2 && c && (Math.abs(a2[0] - b2[0]) < 0.5 && Math.abs(b2[0] - c[0]) < 0.5 || Math.abs(a2[1] - b2[1]) < 0.5 && Math.abs(b2[1] - c[1]) < 0.5))
|
|
452
473
|
continue;
|
|
453
|
-
res.push(
|
|
474
|
+
res.push(b2);
|
|
454
475
|
}
|
|
455
476
|
return res.length >= 2 ? res : [out[0], [out[0][0] + 1, out[0][1]]];
|
|
456
477
|
}
|
|
457
478
|
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
458
479
|
function elementBounds(els) {
|
|
459
|
-
let
|
|
480
|
+
let b2 = null;
|
|
460
481
|
for (const e of els) {
|
|
461
482
|
if (e.isDeleted) continue;
|
|
462
483
|
let x1 = e.x, y1 = e.y, x2 = e.x + e.width, y2 = e.y + e.height;
|
|
@@ -468,15 +489,15 @@ function elementBounds(els) {
|
|
|
468
489
|
y1 = Math.min(...ys);
|
|
469
490
|
y2 = Math.max(...ys);
|
|
470
491
|
}
|
|
471
|
-
if (!
|
|
492
|
+
if (!b2) b2 = { minX: x1, minY: y1, maxX: x2, maxY: y2 };
|
|
472
493
|
else {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
494
|
+
b2.minX = Math.min(b2.minX, x1);
|
|
495
|
+
b2.minY = Math.min(b2.minY, y1);
|
|
496
|
+
b2.maxX = Math.max(b2.maxX, x2);
|
|
497
|
+
b2.maxY = Math.max(b2.maxY, y2);
|
|
477
498
|
}
|
|
478
499
|
}
|
|
479
|
-
return
|
|
500
|
+
return b2;
|
|
480
501
|
}
|
|
481
502
|
function translate(els, dx, dy) {
|
|
482
503
|
for (const e of els) {
|
|
@@ -567,7 +588,7 @@ async function layoutGraph(m) {
|
|
|
567
588
|
for (let p = parentOf.get(id2); p; p = parentOf.get(p)) d++;
|
|
568
589
|
return d;
|
|
569
590
|
};
|
|
570
|
-
for (const g of [...m.groups].sort((
|
|
591
|
+
for (const g of [...m.groups].sort((a2, b2) => depth(a2.id) - depth(b2.id))) {
|
|
571
592
|
const p = pos.get(g.id);
|
|
572
593
|
const c = PALETTE[g.color ?? "gray"];
|
|
573
594
|
const gid = `${m.idPrefix}.g.${g.id}`;
|
|
@@ -619,7 +640,7 @@ async function layoutGraph(m) {
|
|
|
619
640
|
];
|
|
620
641
|
} else continue;
|
|
621
642
|
const isSelf = e.from === e.to;
|
|
622
|
-
const
|
|
643
|
+
const a2 = arrow(`${m.idPrefix}.e.${e.id}`, pts, {
|
|
623
644
|
start: from,
|
|
624
645
|
end: to,
|
|
625
646
|
startArrowhead: e.startArrowhead ?? null,
|
|
@@ -629,8 +650,8 @@ async function layoutGraph(m) {
|
|
|
629
650
|
elbowed: m.elbowed && !isSelf && pts.length > 2,
|
|
630
651
|
customData: m.customData("edge", e.id)
|
|
631
652
|
});
|
|
632
|
-
edgeEls.push(
|
|
633
|
-
if (e.label) edgeEls.push(arrowLabel(
|
|
653
|
+
edgeEls.push(a2);
|
|
654
|
+
if (e.label) edgeEls.push(arrowLabel(a2, e.label, { font: m.look.font }));
|
|
634
655
|
const endLabel = (lbl, p, q, role) => {
|
|
635
656
|
if (!lbl) return;
|
|
636
657
|
const dx = Math.sign(q[0] - p[0]);
|
|
@@ -639,7 +660,7 @@ async function layoutGraph(m) {
|
|
|
639
660
|
const x = p[0] + (dx !== 0 ? dx * 8 + (dx < 0 ? -m2.width : 0) : 8);
|
|
640
661
|
const y = p[1] + (dy !== 0 ? dy * 6 + (dy < 0 ? -m2.height : 0) : -m2.height - 4);
|
|
641
662
|
edgeEls.push(
|
|
642
|
-
text(`${
|
|
663
|
+
text(`${a2.id}.${role}`, x, y, lbl, { fontSize: 14, font: m.look.font, customData: m.customData(role, e.id) })
|
|
643
664
|
);
|
|
644
665
|
};
|
|
645
666
|
endLabel(e.fromLabel, pts[0], pts[1], "from-mult");
|
|
@@ -677,6 +698,43 @@ function labelledNode(ctx, nodeId, label2, kind, colors, opts = {}) {
|
|
|
677
698
|
}
|
|
678
699
|
};
|
|
679
700
|
}
|
|
701
|
+
var CARD_PAD = 10;
|
|
702
|
+
var CARD_GAP = 8;
|
|
703
|
+
var BOUND_TEXT_PADDING = 5;
|
|
704
|
+
function imageCard(ctx, nodeId, label2, fileId, size, colors, opts = {}) {
|
|
705
|
+
const fontSize = opts.fontSize ?? NODE_FONT;
|
|
706
|
+
const natural = ctx.images[fileId];
|
|
707
|
+
const k = size / Math.max(natural.width, natural.height);
|
|
708
|
+
const iw = Math.round(natural.width * k);
|
|
709
|
+
const ih = Math.round(natural.height * k);
|
|
710
|
+
const wrapped = wrapText(label2, fontSize, ctx.look.font, Math.max(MAX_LABEL_W, iw));
|
|
711
|
+
const m = measureText(wrapped, fontSize, ctx.look.font);
|
|
712
|
+
const w = Math.ceil(Math.max(opts.minW ?? 120, iw + CARD_PAD * 2, m.width + 10 + CARD_PAD * 2));
|
|
713
|
+
const h = Math.ceil(CARD_PAD + ih + CARD_GAP + m.height + BOUND_TEXT_PADDING);
|
|
714
|
+
const id2 = `${ctx.idPrefix}.n.${nodeId}`;
|
|
715
|
+
return {
|
|
716
|
+
id: nodeId,
|
|
717
|
+
width: w,
|
|
718
|
+
height: h,
|
|
719
|
+
bindId: id2,
|
|
720
|
+
render: (x, y) => {
|
|
721
|
+
const groupIds = [`${id2}.grp`];
|
|
722
|
+
const s = shape("rectangle", id2, x, y, w, h, {
|
|
723
|
+
strokeColor: colors.stroke,
|
|
724
|
+
backgroundColor: colors.bg,
|
|
725
|
+
strokeStyle: opts.strokeStyle,
|
|
726
|
+
roughness: ctx.look.roughness,
|
|
727
|
+
groupIds,
|
|
728
|
+
customData: ctx.cd("node", nodeId)
|
|
729
|
+
});
|
|
730
|
+
const pic = image(`${id2}.img`, x + (w - iw) / 2, y + CARD_PAD, iw, ih, fileId, { groupIds, customData: ctx.cd("node-image", nodeId) });
|
|
731
|
+
const t = boundText(s, wrapped, { fontSize, font: ctx.look.font, strokeColor: INK, verticalAlign: "bottom" });
|
|
732
|
+
t.y = y + h - BOUND_TEXT_PADDING - t.height;
|
|
733
|
+
return [s, pic, t];
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
var DEFAULT_IMAGE_SIZE = 96;
|
|
680
738
|
var fill = (c) => ({ stroke: INK, bg: PALETTE[c][1] });
|
|
681
739
|
function flowchartModel(spec, ctx) {
|
|
682
740
|
const nodes = spec.nodes.map((n) => {
|
|
@@ -691,6 +749,11 @@ function flowchartModel(spec, ctx) {
|
|
|
691
749
|
note: ["rectangle", "yellow"]
|
|
692
750
|
};
|
|
693
751
|
const [kind, color] = def[shp];
|
|
752
|
+
if (n.image)
|
|
753
|
+
return imageCard(ctx, n.id, n.label, n.image, n.imageSize ?? DEFAULT_IMAGE_SIZE, fill(n.color ?? color), {
|
|
754
|
+
strokeStyle: shp === "note" ? "dashed" : "solid",
|
|
755
|
+
fontSize: shp === "note" ? 16 : NODE_FONT
|
|
756
|
+
});
|
|
694
757
|
return labelledNode(ctx, n.id, n.label, kind, fill(n.color ?? color), {
|
|
695
758
|
strokeStyle: shp === "note" ? "dashed" : "solid",
|
|
696
759
|
fontSize: shp === "note" ? 16 : NODE_FONT
|
|
@@ -725,6 +788,12 @@ function componentModel(spec, ctx) {
|
|
|
725
788
|
if (n.tech) lines.push(`[${n.tech}]`);
|
|
726
789
|
else if (st.caption) lines.push(`\xAB${st.caption}\xBB`);
|
|
727
790
|
if (n.description) lines.push(wrapText(n.description, 16, ctx.look.font, MAX_LABEL_W));
|
|
791
|
+
if (n.image)
|
|
792
|
+
return imageCard(ctx, n.id, lines.join("\n"), n.image, n.imageSize ?? DEFAULT_IMAGE_SIZE, fill(n.color ?? st.color), {
|
|
793
|
+
strokeStyle: st.dashed ? "dashed" : "solid",
|
|
794
|
+
fontSize: n.description ? 16 : 18,
|
|
795
|
+
minW: 160
|
|
796
|
+
});
|
|
728
797
|
return labelledNode(ctx, n.id, lines.join("\n"), st.kind, fill(n.color ?? st.color), {
|
|
729
798
|
strokeStyle: st.dashed ? "dashed" : "solid",
|
|
730
799
|
fontSize: n.description ? 16 : 18,
|
|
@@ -754,10 +823,10 @@ function classModel(spec, ctx) {
|
|
|
754
823
|
const nodes = spec.classes.map((c) => {
|
|
755
824
|
const header = (c.stereotype ? `\xAB${c.stereotype}\xBB
|
|
756
825
|
` : "") + c.name;
|
|
757
|
-
const
|
|
826
|
+
const attrs2 = (c.attributes ?? []).join("\n");
|
|
758
827
|
const meths = (c.methods ?? []).join("\n");
|
|
759
828
|
const hM = measureText(header, 20, font);
|
|
760
|
-
const aM =
|
|
829
|
+
const aM = attrs2 ? measureText(attrs2, 16, font) : { width: 0, height: 20 };
|
|
761
830
|
const mM = meths ? measureText(meths, 16, font) : { width: 0, height: 20 };
|
|
762
831
|
const padX = 14;
|
|
763
832
|
const w = Math.max(180, hM.width + padX * 2 + 10, aM.width + padX * 2, mM.width + padX * 2);
|
|
@@ -802,9 +871,9 @@ function classModel(spec, ctx) {
|
|
|
802
871
|
[x + w, y + hH + aH]
|
|
803
872
|
], { kind: "line", roughness: ctx.look.roughness, groupIds: group, strokeWidth: 1 });
|
|
804
873
|
els.push(div);
|
|
805
|
-
if (
|
|
874
|
+
if (attrs2)
|
|
806
875
|
els.push(
|
|
807
|
-
text(`${id2}.attrs`, x + padX, y + hH + 8,
|
|
876
|
+
text(`${id2}.attrs`, x + padX, y + hH + 8, attrs2, { fontSize: 16, font, groupIds: group, customData: ctx.cd("class-attrs", c.id) })
|
|
808
877
|
);
|
|
809
878
|
if (meths)
|
|
810
879
|
els.push(
|
|
@@ -931,15 +1000,15 @@ function placeRadial(root) {
|
|
|
931
1000
|
const ring = Math.max(220, maxW(root) + 80);
|
|
932
1001
|
const rec = (t, a0, a1, depth) => {
|
|
933
1002
|
const total = leaves(t);
|
|
934
|
-
let
|
|
1003
|
+
let a2 = a0;
|
|
935
1004
|
for (const c of t.children) {
|
|
936
1005
|
const share = (a1 - a0) * leaves(c) / total;
|
|
937
|
-
const mid =
|
|
1006
|
+
const mid = a2 + share / 2;
|
|
938
1007
|
const r = ring * depth + (depth === 1 ? root.w / 4 : 0);
|
|
939
1008
|
c.x = root.x + root.w / 2 + Math.cos(mid) * r - c.w / 2;
|
|
940
1009
|
c.y = root.y + root.h / 2 + Math.sin(mid) * r - c.h / 2;
|
|
941
|
-
rec(c,
|
|
942
|
-
|
|
1010
|
+
rec(c, a2, a2 + share, depth + 1);
|
|
1011
|
+
a2 += share;
|
|
943
1012
|
}
|
|
944
1013
|
};
|
|
945
1014
|
rec(root, -Math.PI / 2, 3 * Math.PI / 2, 1);
|
|
@@ -1024,8 +1093,8 @@ function edgePoint(e, target) {
|
|
|
1024
1093
|
const dy = target[1] - cy;
|
|
1025
1094
|
if (dx === 0 && dy === 0) return [cx, cy];
|
|
1026
1095
|
if (e.type === "ellipse") {
|
|
1027
|
-
const
|
|
1028
|
-
const t2 = 1 / Math.sqrt(dx * dx / (
|
|
1096
|
+
const a2 = e.width / 2, b2 = e.height / 2;
|
|
1097
|
+
const t2 = 1 / Math.sqrt(dx * dx / (a2 * a2) + dy * dy / (b2 * b2));
|
|
1029
1098
|
return [cx + dx * t2, cy + dy * t2];
|
|
1030
1099
|
}
|
|
1031
1100
|
const t = Math.min(e.width / 2 / Math.abs(dx || 1e-9), e.height / 2 / Math.abs(dy || 1e-9));
|
|
@@ -1047,24 +1116,24 @@ function layoutSequence(spec, ctx) {
|
|
|
1047
1116
|
const fit = fitContainer(kind, m.width, m.height, 18, 10);
|
|
1048
1117
|
return { lbl, kind, w: Math.max(130, fit.w), h: Math.max(HEAD_H, fit.h) };
|
|
1049
1118
|
});
|
|
1050
|
-
const headH = Math.max(...boxes.map((
|
|
1119
|
+
const headH = Math.max(...boxes.map((b2) => b2.h));
|
|
1051
1120
|
const gaps = P.map((_, i) => i === 0 ? 0 : Math.max(boxes[i - 1].w / 2 + boxes[i].w / 2 + 50, 180));
|
|
1052
1121
|
const msgLabels = spec.messages.map((m) => m.label ? wrapText(m.label, MSG_FONT, font, 320) : "");
|
|
1053
|
-
const order = spec.messages.map((_, i) => i).sort((
|
|
1122
|
+
const order = spec.messages.map((_, i) => i).sort((a2, b2) => span(a2) - span(b2));
|
|
1054
1123
|
function span(i) {
|
|
1055
1124
|
const m = spec.messages[i];
|
|
1056
1125
|
return Math.abs(idx.get(m.from) - idx.get(m.to));
|
|
1057
1126
|
}
|
|
1058
1127
|
for (const i of order) {
|
|
1059
1128
|
const m = spec.messages[i];
|
|
1060
|
-
const
|
|
1061
|
-
const
|
|
1129
|
+
const a2 = idx.get(m.from);
|
|
1130
|
+
const b2 = idx.get(m.to);
|
|
1062
1131
|
const lw = msgLabels[i] ? measureText(msgLabels[i], MSG_FONT, font).width : 0;
|
|
1063
|
-
if (
|
|
1064
|
-
if (
|
|
1132
|
+
if (a2 === b2) {
|
|
1133
|
+
if (a2 + 1 < P.length) gaps[a2 + 1] = Math.max(gaps[a2 + 1], lw + 90);
|
|
1065
1134
|
continue;
|
|
1066
1135
|
}
|
|
1067
|
-
const [lo, hi] =
|
|
1136
|
+
const [lo, hi] = a2 < b2 ? [a2, b2] : [b2, a2];
|
|
1068
1137
|
let cur = 0;
|
|
1069
1138
|
for (let k = lo + 1; k <= hi; k++) cur += gaps[k];
|
|
1070
1139
|
const need = lw + 60;
|
|
@@ -1108,8 +1177,8 @@ function layoutSequence(spec, ctx) {
|
|
|
1108
1177
|
emitNotes(-1);
|
|
1109
1178
|
spec.messages.forEach((m, i) => {
|
|
1110
1179
|
y += (fragStarts.get(i) ?? 0) * FRAG_HEADER;
|
|
1111
|
-
const
|
|
1112
|
-
const
|
|
1180
|
+
const a2 = idx.get(m.from);
|
|
1181
|
+
const b2 = idx.get(m.to);
|
|
1113
1182
|
const lbl = msgLabels[i];
|
|
1114
1183
|
const lm = lbl ? measureText(lbl, MSG_FONT, font) : { width: 0, height: 0 };
|
|
1115
1184
|
const top = y;
|
|
@@ -1123,29 +1192,29 @@ function layoutSequence(spec, ctx) {
|
|
|
1123
1192
|
groupIds: grp,
|
|
1124
1193
|
customData: ctx.cd("message", String(i))
|
|
1125
1194
|
};
|
|
1126
|
-
if (
|
|
1195
|
+
if (a2 === b2) {
|
|
1127
1196
|
const ay = y + Math.max(lm.height - 6, 0);
|
|
1128
1197
|
els.push(
|
|
1129
1198
|
arrow(
|
|
1130
1199
|
id2,
|
|
1131
1200
|
[
|
|
1132
|
-
[cx[
|
|
1133
|
-
[cx[
|
|
1134
|
-
[cx[
|
|
1135
|
-
[cx[
|
|
1201
|
+
[cx[a2], ay],
|
|
1202
|
+
[cx[a2] + 50, ay],
|
|
1203
|
+
[cx[a2] + 50, ay + 34],
|
|
1204
|
+
[cx[a2] + 4, ay + 34]
|
|
1136
1205
|
],
|
|
1137
1206
|
common2
|
|
1138
1207
|
)
|
|
1139
1208
|
);
|
|
1140
|
-
if (lbl) els.push(text(`${id2}.t`, cx[
|
|
1209
|
+
if (lbl) els.push(text(`${id2}.t`, cx[a2] + 60, ay + 17 - lm.height / 2, lbl, { fontSize: MSG_FONT, font, groupIds: grp, customData: ctx.cd("message-label", String(i)) }));
|
|
1141
1210
|
y = ay + 34 + 26;
|
|
1142
1211
|
} else {
|
|
1143
1212
|
const ay = y + lm.height + 6;
|
|
1144
|
-
const dir = Math.sign(cx[
|
|
1145
|
-
els.push(arrow(id2, [[cx[
|
|
1213
|
+
const dir = Math.sign(cx[b2] - cx[a2]);
|
|
1214
|
+
els.push(arrow(id2, [[cx[a2], ay], [cx[b2] - dir * 2, ay]], common2));
|
|
1146
1215
|
if (lbl)
|
|
1147
1216
|
els.push(
|
|
1148
|
-
text(`${id2}.t`, (cx[
|
|
1217
|
+
text(`${id2}.t`, (cx[a2] + cx[b2]) / 2 - lm.width / 2, y, lbl, {
|
|
1149
1218
|
fontSize: MSG_FONT,
|
|
1150
1219
|
font,
|
|
1151
1220
|
textAlign: "center",
|
|
@@ -1190,15 +1259,15 @@ function layoutSequence(spec, ctx) {
|
|
|
1190
1259
|
});
|
|
1191
1260
|
const headEls = [];
|
|
1192
1261
|
P.forEach((p, i) => {
|
|
1193
|
-
const
|
|
1262
|
+
const b2 = boxes[i];
|
|
1194
1263
|
const id2 = `${ctx.idPrefix}.p.${p.id}`;
|
|
1195
1264
|
const color = PALETTE[p.type === "actor" ? "pink" : p.type === "database" ? "cyan" : p.type === "external" ? "gray" : "blue"];
|
|
1196
|
-
const s = shape(
|
|
1265
|
+
const s = shape(b2.kind, id2, cx[i] - b2.w / 2, (headH - b2.h) / 2, b2.w, b2.h, {
|
|
1197
1266
|
backgroundColor: color[1],
|
|
1198
1267
|
roughness,
|
|
1199
1268
|
customData: ctx.cd("participant", p.id)
|
|
1200
1269
|
});
|
|
1201
|
-
headEls.push(s, boundText(s,
|
|
1270
|
+
headEls.push(s, boundText(s, b2.lbl, { fontSize: 18, font, strokeColor: INK }));
|
|
1202
1271
|
headEls.push(
|
|
1203
1272
|
arrow(`${id2}.life`, [[cx[i], headH], [cx[i], bottom]], {
|
|
1204
1273
|
kind: "line",
|
|
@@ -1215,7 +1284,7 @@ function layoutSequence(spec, ctx) {
|
|
|
1215
1284
|
|
|
1216
1285
|
// ../diagram/src/render.ts
|
|
1217
1286
|
var FRAME_PAD = 40;
|
|
1218
|
-
var LAYOUT_VERSION =
|
|
1287
|
+
var LAYOUT_VERSION = 2;
|
|
1219
1288
|
var SpecError = class extends Error {
|
|
1220
1289
|
constructor(issues) {
|
|
1221
1290
|
super(`Invalid diagram spec:
|
|
@@ -1238,7 +1307,10 @@ async function renderDiagram(spec, opts) {
|
|
|
1238
1307
|
const look = LOOKS[spec.style ?? "sketch"];
|
|
1239
1308
|
const idPrefix = opts.diagramId;
|
|
1240
1309
|
const cd = (role, ref) => vizoData(opts.diagramId, role, ref);
|
|
1241
|
-
const
|
|
1310
|
+
const images = opts.images ?? {};
|
|
1311
|
+
const missing = specImageIds(spec).filter((id2) => !images[id2]);
|
|
1312
|
+
if (missing.length) throw new SpecError(missing.map((id2) => `image '${id2}' is not on this board; upload it with add_image and use the fileId it returns`));
|
|
1313
|
+
const ctx = { idPrefix, look, cd, images };
|
|
1242
1314
|
let els;
|
|
1243
1315
|
switch (spec.kind) {
|
|
1244
1316
|
case "flowchart":
|
|
@@ -1260,11 +1332,11 @@ async function renderDiagram(spec, opts) {
|
|
|
1260
1332
|
els = layoutMindmap(spec, ctx);
|
|
1261
1333
|
break;
|
|
1262
1334
|
}
|
|
1263
|
-
const
|
|
1335
|
+
const b2 = elementBounds(els);
|
|
1264
1336
|
const origin = opts.origin ?? { x: 0, y: 0 };
|
|
1265
|
-
translate(els, origin.x + FRAME_PAD -
|
|
1266
|
-
const w =
|
|
1267
|
-
const h =
|
|
1337
|
+
translate(els, origin.x + FRAME_PAD - b2.minX, origin.y + FRAME_PAD - b2.minY);
|
|
1338
|
+
const w = b2.maxX - b2.minX + FRAME_PAD * 2;
|
|
1339
|
+
const h = b2.maxY - b2.minY + FRAME_PAD * 2;
|
|
1268
1340
|
const frameId = `${opts.diagramId}.frame`;
|
|
1269
1341
|
const f = frame(frameId, origin.x, origin.y, w, h, spec.title ?? defaultTitle(spec), {
|
|
1270
1342
|
customData: cd("frame", opts.diagramId)
|
|
@@ -1307,105 +1379,2855 @@ var ConnectInput = z2.object({
|
|
|
1307
1379
|
color: Color.optional()
|
|
1308
1380
|
});
|
|
1309
1381
|
|
|
1310
|
-
// src/
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
if (
|
|
1318
|
-
|
|
1319
|
-
|
|
1382
|
+
// src/export.ts
|
|
1383
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
1384
|
+
import { dirname, extname, resolve } from "node:path";
|
|
1385
|
+
import { fileURLToPath } from "node:url";
|
|
1386
|
+
|
|
1387
|
+
// ../../node_modules/.pnpm/hachure-fill@0.5.2/node_modules/hachure-fill/bin/hachure.js
|
|
1388
|
+
function rotatePoints(points, center, degrees) {
|
|
1389
|
+
if (points && points.length) {
|
|
1390
|
+
const [cx, cy] = center;
|
|
1391
|
+
const angle = Math.PI / 180 * degrees;
|
|
1392
|
+
const cos = Math.cos(angle);
|
|
1393
|
+
const sin = Math.sin(angle);
|
|
1394
|
+
for (const p of points) {
|
|
1395
|
+
const [x, y] = p;
|
|
1396
|
+
p[0] = (x - cx) * cos - (y - cy) * sin + cx;
|
|
1397
|
+
p[1] = (x - cx) * sin + (y - cy) * cos + cy;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1320
1400
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1401
|
+
function rotateLines(lines, center, degrees) {
|
|
1402
|
+
const points = [];
|
|
1403
|
+
lines.forEach((line2) => points.push(...line2));
|
|
1404
|
+
rotatePoints(points, center, degrees);
|
|
1405
|
+
}
|
|
1406
|
+
function areSamePoints(p1, p2) {
|
|
1407
|
+
return p1[0] === p2[0] && p1[1] === p2[1];
|
|
1408
|
+
}
|
|
1409
|
+
function hachureLines(polygons, hachureGap, hachureAngle, hachureStepOffset = 1) {
|
|
1410
|
+
const angle = hachureAngle;
|
|
1411
|
+
const gap = Math.max(hachureGap, 0.1);
|
|
1412
|
+
const polygonList = polygons[0] && polygons[0][0] && typeof polygons[0][0] === "number" ? [polygons] : polygons;
|
|
1413
|
+
const rotationCenter = [0, 0];
|
|
1414
|
+
if (angle) {
|
|
1415
|
+
for (const polygon2 of polygonList) {
|
|
1416
|
+
rotatePoints(polygon2, rotationCenter, angle);
|
|
1417
|
+
}
|
|
1329
1418
|
}
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
);
|
|
1335
|
-
const caps = upstream.getServerCapabilities() ?? {};
|
|
1336
|
-
log(`connected to ${endpoint.href} (local layout v${LAYOUT_VERSION})`);
|
|
1337
|
-
const server = new Server(
|
|
1338
|
-
{ name: "vizo", version: "0.1.0" },
|
|
1339
|
-
{
|
|
1340
|
-
capabilities: {
|
|
1341
|
-
tools: caps.tools ? {} : void 0,
|
|
1342
|
-
resources: caps.resources ? {} : void 0,
|
|
1343
|
-
prompts: caps.prompts ? {} : void 0
|
|
1344
|
-
},
|
|
1345
|
-
instructions: upstream.getInstructions()
|
|
1419
|
+
const lines = straightHachureLines(polygonList, gap, hachureStepOffset);
|
|
1420
|
+
if (angle) {
|
|
1421
|
+
for (const polygon2 of polygonList) {
|
|
1422
|
+
rotatePoints(polygon2, rotationCenter, -angle);
|
|
1346
1423
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1424
|
+
rotateLines(lines, rotationCenter, -angle);
|
|
1425
|
+
}
|
|
1426
|
+
return lines;
|
|
1427
|
+
}
|
|
1428
|
+
function straightHachureLines(polygons, gap, hachureStepOffset) {
|
|
1429
|
+
const vertexArray = [];
|
|
1430
|
+
for (const polygon2 of polygons) {
|
|
1431
|
+
const vertices = [...polygon2];
|
|
1432
|
+
if (!areSamePoints(vertices[0], vertices[vertices.length - 1])) {
|
|
1433
|
+
vertices.push([vertices[0][0], vertices[0][1]]);
|
|
1434
|
+
}
|
|
1435
|
+
if (vertices.length > 2) {
|
|
1436
|
+
vertexArray.push(vertices);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
const lines = [];
|
|
1440
|
+
gap = Math.max(gap, 0.1);
|
|
1441
|
+
const edges = [];
|
|
1442
|
+
for (const vertices of vertexArray) {
|
|
1443
|
+
for (let i = 0; i < vertices.length - 1; i++) {
|
|
1444
|
+
const p1 = vertices[i];
|
|
1445
|
+
const p2 = vertices[i + 1];
|
|
1446
|
+
if (p1[1] !== p2[1]) {
|
|
1447
|
+
const ymin = Math.min(p1[1], p2[1]);
|
|
1448
|
+
edges.push({
|
|
1449
|
+
ymin,
|
|
1450
|
+
ymax: Math.max(p1[1], p2[1]),
|
|
1451
|
+
x: ymin === p1[1] ? p1[0] : p2[0],
|
|
1452
|
+
islope: (p2[0] - p1[0]) / (p2[1] - p1[1])
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
edges.sort((e1, e2) => {
|
|
1458
|
+
if (e1.ymin < e2.ymin) {
|
|
1459
|
+
return -1;
|
|
1460
|
+
}
|
|
1461
|
+
if (e1.ymin > e2.ymin) {
|
|
1462
|
+
return 1;
|
|
1463
|
+
}
|
|
1464
|
+
if (e1.x < e2.x) {
|
|
1465
|
+
return -1;
|
|
1466
|
+
}
|
|
1467
|
+
if (e1.x > e2.x) {
|
|
1468
|
+
return 1;
|
|
1469
|
+
}
|
|
1470
|
+
if (e1.ymax === e2.ymax) {
|
|
1471
|
+
return 0;
|
|
1472
|
+
}
|
|
1473
|
+
return (e1.ymax - e2.ymax) / Math.abs(e1.ymax - e2.ymax);
|
|
1474
|
+
});
|
|
1475
|
+
if (!edges.length) {
|
|
1476
|
+
return lines;
|
|
1477
|
+
}
|
|
1478
|
+
let activeEdges = [];
|
|
1479
|
+
let y = edges[0].ymin;
|
|
1480
|
+
let iteration = 0;
|
|
1481
|
+
while (activeEdges.length || edges.length) {
|
|
1482
|
+
if (edges.length) {
|
|
1483
|
+
let ix = -1;
|
|
1484
|
+
for (let i = 0; i < edges.length; i++) {
|
|
1485
|
+
if (edges[i].ymin > y) {
|
|
1486
|
+
break;
|
|
1487
|
+
}
|
|
1488
|
+
ix = i;
|
|
1489
|
+
}
|
|
1490
|
+
const removed = edges.splice(0, ix + 1);
|
|
1491
|
+
removed.forEach((edge) => {
|
|
1492
|
+
activeEdges.push({ s: y, edge });
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
activeEdges = activeEdges.filter((ae2) => {
|
|
1496
|
+
if (ae2.edge.ymax <= y) {
|
|
1497
|
+
return false;
|
|
1498
|
+
}
|
|
1499
|
+
return true;
|
|
1354
1500
|
});
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
serverSupportsLocal ??= (await upstream.listTools()).tools.some((t) => t.name === PUT_TOOL);
|
|
1359
|
-
if (!serverSupportsLocal) {
|
|
1360
|
-
return toolError(
|
|
1361
|
-
`This Vizo server (${base2}) does not accept locally rendered diagrams (no ${PUT_TOOL}); it is older than this vizo-mcp (layout v${LAYOUT_VERSION}). Nothing was drawn. Update the server, or use a vizo-mcp version matching it.`
|
|
1362
|
-
);
|
|
1501
|
+
activeEdges.sort((ae1, ae2) => {
|
|
1502
|
+
if (ae1.edge.x === ae2.edge.x) {
|
|
1503
|
+
return 0;
|
|
1363
1504
|
}
|
|
1364
|
-
return
|
|
1505
|
+
return (ae1.edge.x - ae2.edge.x) / Math.abs(ae1.edge.x - ae2.edge.x);
|
|
1506
|
+
});
|
|
1507
|
+
if (hachureStepOffset !== 1 || iteration % gap === 0) {
|
|
1508
|
+
if (activeEdges.length > 1) {
|
|
1509
|
+
for (let i = 0; i < activeEdges.length; i = i + 2) {
|
|
1510
|
+
const nexti = i + 1;
|
|
1511
|
+
if (nexti >= activeEdges.length) {
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1514
|
+
const ce2 = activeEdges[i].edge;
|
|
1515
|
+
const ne = activeEdges[nexti].edge;
|
|
1516
|
+
lines.push([
|
|
1517
|
+
[Math.round(ce2.x), y],
|
|
1518
|
+
[Math.round(ne.x), y]
|
|
1519
|
+
]);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
y += hachureStepOffset;
|
|
1524
|
+
activeEdges.forEach((ae2) => {
|
|
1525
|
+
ae2.edge.x = ae2.edge.x + hachureStepOffset * ae2.edge.islope;
|
|
1365
1526
|
});
|
|
1527
|
+
iteration++;
|
|
1366
1528
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1529
|
+
return lines;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/fillers/scan-line-hachure.js
|
|
1533
|
+
function polygonHachureLines(polygonList, o) {
|
|
1534
|
+
var _a;
|
|
1535
|
+
const angle = o.hachureAngle + 90;
|
|
1536
|
+
let gap = o.hachureGap;
|
|
1537
|
+
if (gap < 0) {
|
|
1538
|
+
gap = o.strokeWidth * 4;
|
|
1371
1539
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1540
|
+
gap = Math.round(Math.max(gap, 0.1));
|
|
1541
|
+
let skipOffset = 1;
|
|
1542
|
+
if (o.roughness >= 1) {
|
|
1543
|
+
if ((((_a = o.randomizer) === null || _a === void 0 ? void 0 : _a.next()) || Math.random()) > 0.7) {
|
|
1544
|
+
skipOffset = gap;
|
|
1545
|
+
}
|
|
1375
1546
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1547
|
+
return hachureLines(polygonList, gap, angle, skipOffset || 1);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/fillers/hachure-filler.js
|
|
1551
|
+
var HachureFiller = class {
|
|
1552
|
+
constructor(helper2) {
|
|
1553
|
+
this.helper = helper2;
|
|
1554
|
+
}
|
|
1555
|
+
fillPolygons(polygonList, o) {
|
|
1556
|
+
return this._fillPolygons(polygonList, o);
|
|
1557
|
+
}
|
|
1558
|
+
_fillPolygons(polygonList, o) {
|
|
1559
|
+
const lines = polygonHachureLines(polygonList, o);
|
|
1560
|
+
const ops = this.renderLines(lines, o);
|
|
1561
|
+
return { type: "fillSketch", ops };
|
|
1562
|
+
}
|
|
1563
|
+
renderLines(lines, o) {
|
|
1564
|
+
const ops = [];
|
|
1565
|
+
for (const line2 of lines) {
|
|
1566
|
+
ops.push(...this.helper.doubleLineOps(line2[0][0], line2[0][1], line2[1][0], line2[1][1], o));
|
|
1383
1567
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1568
|
+
return ops;
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
|
|
1572
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/geometry.js
|
|
1573
|
+
function lineLength(line2) {
|
|
1574
|
+
const p1 = line2[0];
|
|
1575
|
+
const p2 = line2[1];
|
|
1576
|
+
return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/fillers/zigzag-filler.js
|
|
1580
|
+
var ZigZagFiller = class extends HachureFiller {
|
|
1581
|
+
fillPolygons(polygonList, o) {
|
|
1582
|
+
let gap = o.hachureGap;
|
|
1583
|
+
if (gap < 0) {
|
|
1584
|
+
gap = o.strokeWidth * 4;
|
|
1585
|
+
}
|
|
1586
|
+
gap = Math.max(gap, 0.1);
|
|
1587
|
+
const o2 = Object.assign({}, o, { hachureGap: gap });
|
|
1588
|
+
const lines = polygonHachureLines(polygonList, o2);
|
|
1589
|
+
const zigZagAngle = Math.PI / 180 * o.hachureAngle;
|
|
1590
|
+
const zigzagLines = [];
|
|
1591
|
+
const dgx = gap * 0.5 * Math.cos(zigZagAngle);
|
|
1592
|
+
const dgy = gap * 0.5 * Math.sin(zigZagAngle);
|
|
1593
|
+
for (const [p1, p2] of lines) {
|
|
1594
|
+
if (lineLength([p1, p2])) {
|
|
1595
|
+
zigzagLines.push([
|
|
1596
|
+
[p1[0] - dgx, p1[1] + dgy],
|
|
1597
|
+
[...p2]
|
|
1598
|
+
], [
|
|
1599
|
+
[p1[0] + dgx, p1[1] - dgy],
|
|
1600
|
+
[...p2]
|
|
1601
|
+
]);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
const ops = this.renderLines(zigzagLines, o);
|
|
1605
|
+
return { type: "fillSketch", ops };
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/fillers/hatch-filler.js
|
|
1610
|
+
var HatchFiller = class extends HachureFiller {
|
|
1611
|
+
fillPolygons(polygonList, o) {
|
|
1612
|
+
const set = this._fillPolygons(polygonList, o);
|
|
1613
|
+
const o2 = Object.assign({}, o, { hachureAngle: o.hachureAngle + 90 });
|
|
1614
|
+
const set2 = this._fillPolygons(polygonList, o2);
|
|
1615
|
+
set.ops = set.ops.concat(set2.ops);
|
|
1616
|
+
return set;
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
|
|
1620
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/fillers/dot-filler.js
|
|
1621
|
+
var DotFiller = class {
|
|
1622
|
+
constructor(helper2) {
|
|
1623
|
+
this.helper = helper2;
|
|
1624
|
+
}
|
|
1625
|
+
fillPolygons(polygonList, o) {
|
|
1626
|
+
o = Object.assign({}, o, { hachureAngle: 0 });
|
|
1627
|
+
const lines = polygonHachureLines(polygonList, o);
|
|
1628
|
+
return this.dotsOnLines(lines, o);
|
|
1629
|
+
}
|
|
1630
|
+
dotsOnLines(lines, o) {
|
|
1631
|
+
const ops = [];
|
|
1632
|
+
let gap = o.hachureGap;
|
|
1633
|
+
if (gap < 0) {
|
|
1634
|
+
gap = o.strokeWidth * 4;
|
|
1635
|
+
}
|
|
1636
|
+
gap = Math.max(gap, 0.1);
|
|
1637
|
+
let fweight = o.fillWeight;
|
|
1638
|
+
if (fweight < 0) {
|
|
1639
|
+
fweight = o.strokeWidth / 2;
|
|
1640
|
+
}
|
|
1641
|
+
const ro = gap / 4;
|
|
1642
|
+
for (const line2 of lines) {
|
|
1643
|
+
const length = lineLength(line2);
|
|
1644
|
+
const dl = length / gap;
|
|
1645
|
+
const count = Math.ceil(dl) - 1;
|
|
1646
|
+
const offset = length - count * gap;
|
|
1647
|
+
const x = (line2[0][0] + line2[1][0]) / 2 - gap / 4;
|
|
1648
|
+
const minY = Math.min(line2[0][1], line2[1][1]);
|
|
1649
|
+
for (let i = 0; i < count; i++) {
|
|
1650
|
+
const y = minY + offset + i * gap;
|
|
1651
|
+
const cx = x - ro + Math.random() * 2 * ro;
|
|
1652
|
+
const cy = y - ro + Math.random() * 2 * ro;
|
|
1653
|
+
const el = this.helper.ellipse(cx, cy, fweight, fweight, o);
|
|
1654
|
+
ops.push(...el.ops);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
return { type: "fillSketch", ops };
|
|
1658
|
+
}
|
|
1659
|
+
};
|
|
1660
|
+
|
|
1661
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/fillers/dashed-filler.js
|
|
1662
|
+
var DashedFiller = class {
|
|
1663
|
+
constructor(helper2) {
|
|
1664
|
+
this.helper = helper2;
|
|
1665
|
+
}
|
|
1666
|
+
fillPolygons(polygonList, o) {
|
|
1667
|
+
const lines = polygonHachureLines(polygonList, o);
|
|
1668
|
+
return { type: "fillSketch", ops: this.dashedLine(lines, o) };
|
|
1669
|
+
}
|
|
1670
|
+
dashedLine(lines, o) {
|
|
1671
|
+
const offset = o.dashOffset < 0 ? o.hachureGap < 0 ? o.strokeWidth * 4 : o.hachureGap : o.dashOffset;
|
|
1672
|
+
const gap = o.dashGap < 0 ? o.hachureGap < 0 ? o.strokeWidth * 4 : o.hachureGap : o.dashGap;
|
|
1673
|
+
const ops = [];
|
|
1674
|
+
lines.forEach((line2) => {
|
|
1675
|
+
const length = lineLength(line2);
|
|
1676
|
+
const count = Math.floor(length / (offset + gap));
|
|
1677
|
+
const startOffset = (length + gap - count * (offset + gap)) / 2;
|
|
1678
|
+
let p1 = line2[0];
|
|
1679
|
+
let p2 = line2[1];
|
|
1680
|
+
if (p1[0] > p2[0]) {
|
|
1681
|
+
p1 = line2[1];
|
|
1682
|
+
p2 = line2[0];
|
|
1683
|
+
}
|
|
1684
|
+
const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
|
|
1685
|
+
for (let i = 0; i < count; i++) {
|
|
1686
|
+
const lstart = i * (offset + gap);
|
|
1687
|
+
const lend = lstart + offset;
|
|
1688
|
+
const start = [p1[0] + lstart * Math.cos(alpha) + startOffset * Math.cos(alpha), p1[1] + lstart * Math.sin(alpha) + startOffset * Math.sin(alpha)];
|
|
1689
|
+
const end = [p1[0] + lend * Math.cos(alpha) + startOffset * Math.cos(alpha), p1[1] + lend * Math.sin(alpha) + startOffset * Math.sin(alpha)];
|
|
1690
|
+
ops.push(...this.helper.doubleLineOps(start[0], start[1], end[0], end[1], o));
|
|
1398
1691
|
}
|
|
1399
1692
|
});
|
|
1693
|
+
return ops;
|
|
1400
1694
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1695
|
+
};
|
|
1696
|
+
|
|
1697
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/fillers/zigzag-line-filler.js
|
|
1698
|
+
var ZigZagLineFiller = class {
|
|
1699
|
+
constructor(helper2) {
|
|
1700
|
+
this.helper = helper2;
|
|
1701
|
+
}
|
|
1702
|
+
fillPolygons(polygonList, o) {
|
|
1703
|
+
const gap = o.hachureGap < 0 ? o.strokeWidth * 4 : o.hachureGap;
|
|
1704
|
+
const zo = o.zigzagOffset < 0 ? gap : o.zigzagOffset;
|
|
1705
|
+
o = Object.assign({}, o, { hachureGap: gap + zo });
|
|
1706
|
+
const lines = polygonHachureLines(polygonList, o);
|
|
1707
|
+
return { type: "fillSketch", ops: this.zigzagLines(lines, zo, o) };
|
|
1708
|
+
}
|
|
1709
|
+
zigzagLines(lines, zo, o) {
|
|
1710
|
+
const ops = [];
|
|
1711
|
+
lines.forEach((line2) => {
|
|
1712
|
+
const length = lineLength(line2);
|
|
1713
|
+
const count = Math.round(length / (2 * zo));
|
|
1714
|
+
let p1 = line2[0];
|
|
1715
|
+
let p2 = line2[1];
|
|
1716
|
+
if (p1[0] > p2[0]) {
|
|
1717
|
+
p1 = line2[1];
|
|
1718
|
+
p2 = line2[0];
|
|
1719
|
+
}
|
|
1720
|
+
const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
|
|
1721
|
+
for (let i = 0; i < count; i++) {
|
|
1722
|
+
const lstart = i * 2 * zo;
|
|
1723
|
+
const lend = (i + 1) * 2 * zo;
|
|
1724
|
+
const dz = Math.sqrt(2 * Math.pow(zo, 2));
|
|
1725
|
+
const start = [p1[0] + lstart * Math.cos(alpha), p1[1] + lstart * Math.sin(alpha)];
|
|
1726
|
+
const end = [p1[0] + lend * Math.cos(alpha), p1[1] + lend * Math.sin(alpha)];
|
|
1727
|
+
const middle = [start[0] + dz * Math.cos(alpha + Math.PI / 4), start[1] + dz * Math.sin(alpha + Math.PI / 4)];
|
|
1728
|
+
ops.push(...this.helper.doubleLineOps(start[0], start[1], middle[0], middle[1], o), ...this.helper.doubleLineOps(middle[0], middle[1], end[0], end[1], o));
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
return ops;
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1735
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/fillers/filler.js
|
|
1736
|
+
var fillers = {};
|
|
1737
|
+
function getFiller(o, helper2) {
|
|
1738
|
+
let fillerName = o.fillStyle || "hachure";
|
|
1739
|
+
if (!fillers[fillerName]) {
|
|
1740
|
+
switch (fillerName) {
|
|
1741
|
+
case "zigzag":
|
|
1742
|
+
if (!fillers[fillerName]) {
|
|
1743
|
+
fillers[fillerName] = new ZigZagFiller(helper2);
|
|
1744
|
+
}
|
|
1745
|
+
break;
|
|
1746
|
+
case "cross-hatch":
|
|
1747
|
+
if (!fillers[fillerName]) {
|
|
1748
|
+
fillers[fillerName] = new HatchFiller(helper2);
|
|
1749
|
+
}
|
|
1750
|
+
break;
|
|
1751
|
+
case "dots":
|
|
1752
|
+
if (!fillers[fillerName]) {
|
|
1753
|
+
fillers[fillerName] = new DotFiller(helper2);
|
|
1754
|
+
}
|
|
1755
|
+
break;
|
|
1756
|
+
case "dashed":
|
|
1757
|
+
if (!fillers[fillerName]) {
|
|
1758
|
+
fillers[fillerName] = new DashedFiller(helper2);
|
|
1759
|
+
}
|
|
1760
|
+
break;
|
|
1761
|
+
case "zigzag-line":
|
|
1762
|
+
if (!fillers[fillerName]) {
|
|
1763
|
+
fillers[fillerName] = new ZigZagLineFiller(helper2);
|
|
1764
|
+
}
|
|
1765
|
+
break;
|
|
1766
|
+
case "hachure":
|
|
1767
|
+
default:
|
|
1768
|
+
fillerName = "hachure";
|
|
1769
|
+
if (!fillers[fillerName]) {
|
|
1770
|
+
fillers[fillerName] = new HachureFiller(helper2);
|
|
1771
|
+
}
|
|
1772
|
+
break;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
return fillers[fillerName];
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/math.js
|
|
1779
|
+
function randomSeed() {
|
|
1780
|
+
return Math.floor(Math.random() * 2 ** 31);
|
|
1781
|
+
}
|
|
1782
|
+
var Random = class {
|
|
1783
|
+
constructor(seed) {
|
|
1784
|
+
this.seed = seed;
|
|
1785
|
+
}
|
|
1786
|
+
next() {
|
|
1787
|
+
if (this.seed) {
|
|
1788
|
+
return (2 ** 31 - 1 & (this.seed = Math.imul(48271, this.seed))) / 2 ** 31;
|
|
1789
|
+
} else {
|
|
1790
|
+
return Math.random();
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
|
|
1795
|
+
// ../../node_modules/.pnpm/path-data-parser@0.1.0/node_modules/path-data-parser/lib/parser.js
|
|
1796
|
+
var COMMAND = 0;
|
|
1797
|
+
var NUMBER = 1;
|
|
1798
|
+
var EOD = 2;
|
|
1799
|
+
var PARAMS = { A: 7, a: 7, C: 6, c: 6, H: 1, h: 1, L: 2, l: 2, M: 2, m: 2, Q: 4, q: 4, S: 4, s: 4, T: 2, t: 2, V: 1, v: 1, Z: 0, z: 0 };
|
|
1800
|
+
function tokenize(d) {
|
|
1801
|
+
const tokens = new Array();
|
|
1802
|
+
while (d !== "") {
|
|
1803
|
+
if (d.match(/^([ \t\r\n,]+)/)) {
|
|
1804
|
+
d = d.substr(RegExp.$1.length);
|
|
1805
|
+
} else if (d.match(/^([aAcChHlLmMqQsStTvVzZ])/)) {
|
|
1806
|
+
tokens[tokens.length] = { type: COMMAND, text: RegExp.$1 };
|
|
1807
|
+
d = d.substr(RegExp.$1.length);
|
|
1808
|
+
} else if (d.match(/^(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/)) {
|
|
1809
|
+
tokens[tokens.length] = { type: NUMBER, text: `${parseFloat(RegExp.$1)}` };
|
|
1810
|
+
d = d.substr(RegExp.$1.length);
|
|
1811
|
+
} else {
|
|
1812
|
+
return [];
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
tokens[tokens.length] = { type: EOD, text: "" };
|
|
1816
|
+
return tokens;
|
|
1817
|
+
}
|
|
1818
|
+
function isType(token, type) {
|
|
1819
|
+
return token.type === type;
|
|
1820
|
+
}
|
|
1821
|
+
function parsePath(d) {
|
|
1822
|
+
const segments = [];
|
|
1823
|
+
const tokens = tokenize(d);
|
|
1824
|
+
let mode = "BOD";
|
|
1825
|
+
let index = 0;
|
|
1826
|
+
let token = tokens[index];
|
|
1827
|
+
while (!isType(token, EOD)) {
|
|
1828
|
+
let paramsCount = 0;
|
|
1829
|
+
const params = [];
|
|
1830
|
+
if (mode === "BOD") {
|
|
1831
|
+
if (token.text === "M" || token.text === "m") {
|
|
1832
|
+
index++;
|
|
1833
|
+
paramsCount = PARAMS[token.text];
|
|
1834
|
+
mode = token.text;
|
|
1835
|
+
} else {
|
|
1836
|
+
return parsePath("M0,0" + d);
|
|
1837
|
+
}
|
|
1838
|
+
} else if (isType(token, NUMBER)) {
|
|
1839
|
+
paramsCount = PARAMS[mode];
|
|
1840
|
+
} else {
|
|
1841
|
+
index++;
|
|
1842
|
+
paramsCount = PARAMS[token.text];
|
|
1843
|
+
mode = token.text;
|
|
1844
|
+
}
|
|
1845
|
+
if (index + paramsCount < tokens.length) {
|
|
1846
|
+
for (let i = index; i < index + paramsCount; i++) {
|
|
1847
|
+
const numbeToken = tokens[i];
|
|
1848
|
+
if (isType(numbeToken, NUMBER)) {
|
|
1849
|
+
params[params.length] = +numbeToken.text;
|
|
1850
|
+
} else {
|
|
1851
|
+
throw new Error("Param not a number: " + mode + "," + numbeToken.text);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
if (typeof PARAMS[mode] === "number") {
|
|
1855
|
+
const segment = { key: mode, data: params };
|
|
1856
|
+
segments.push(segment);
|
|
1857
|
+
index += paramsCount;
|
|
1858
|
+
token = tokens[index];
|
|
1859
|
+
if (mode === "M")
|
|
1860
|
+
mode = "L";
|
|
1861
|
+
if (mode === "m")
|
|
1862
|
+
mode = "l";
|
|
1863
|
+
} else {
|
|
1864
|
+
throw new Error("Bad segment: " + mode);
|
|
1865
|
+
}
|
|
1866
|
+
} else {
|
|
1867
|
+
throw new Error("Path data ended short");
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
return segments;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// ../../node_modules/.pnpm/path-data-parser@0.1.0/node_modules/path-data-parser/lib/absolutize.js
|
|
1874
|
+
function absolutize(segments) {
|
|
1875
|
+
let cx = 0, cy = 0;
|
|
1876
|
+
let subx = 0, suby = 0;
|
|
1877
|
+
const out = [];
|
|
1878
|
+
for (const { key, data } of segments) {
|
|
1879
|
+
switch (key) {
|
|
1880
|
+
case "M":
|
|
1881
|
+
out.push({ key: "M", data: [...data] });
|
|
1882
|
+
[cx, cy] = data;
|
|
1883
|
+
[subx, suby] = data;
|
|
1884
|
+
break;
|
|
1885
|
+
case "m":
|
|
1886
|
+
cx += data[0];
|
|
1887
|
+
cy += data[1];
|
|
1888
|
+
out.push({ key: "M", data: [cx, cy] });
|
|
1889
|
+
subx = cx;
|
|
1890
|
+
suby = cy;
|
|
1891
|
+
break;
|
|
1892
|
+
case "L":
|
|
1893
|
+
out.push({ key: "L", data: [...data] });
|
|
1894
|
+
[cx, cy] = data;
|
|
1895
|
+
break;
|
|
1896
|
+
case "l":
|
|
1897
|
+
cx += data[0];
|
|
1898
|
+
cy += data[1];
|
|
1899
|
+
out.push({ key: "L", data: [cx, cy] });
|
|
1900
|
+
break;
|
|
1901
|
+
case "C":
|
|
1902
|
+
out.push({ key: "C", data: [...data] });
|
|
1903
|
+
cx = data[4];
|
|
1904
|
+
cy = data[5];
|
|
1905
|
+
break;
|
|
1906
|
+
case "c": {
|
|
1907
|
+
const newdata = data.map((d, i) => i % 2 ? d + cy : d + cx);
|
|
1908
|
+
out.push({ key: "C", data: newdata });
|
|
1909
|
+
cx = newdata[4];
|
|
1910
|
+
cy = newdata[5];
|
|
1911
|
+
break;
|
|
1912
|
+
}
|
|
1913
|
+
case "Q":
|
|
1914
|
+
out.push({ key: "Q", data: [...data] });
|
|
1915
|
+
cx = data[2];
|
|
1916
|
+
cy = data[3];
|
|
1917
|
+
break;
|
|
1918
|
+
case "q": {
|
|
1919
|
+
const newdata = data.map((d, i) => i % 2 ? d + cy : d + cx);
|
|
1920
|
+
out.push({ key: "Q", data: newdata });
|
|
1921
|
+
cx = newdata[2];
|
|
1922
|
+
cy = newdata[3];
|
|
1923
|
+
break;
|
|
1924
|
+
}
|
|
1925
|
+
case "A":
|
|
1926
|
+
out.push({ key: "A", data: [...data] });
|
|
1927
|
+
cx = data[5];
|
|
1928
|
+
cy = data[6];
|
|
1929
|
+
break;
|
|
1930
|
+
case "a":
|
|
1931
|
+
cx += data[5];
|
|
1932
|
+
cy += data[6];
|
|
1933
|
+
out.push({ key: "A", data: [data[0], data[1], data[2], data[3], data[4], cx, cy] });
|
|
1934
|
+
break;
|
|
1935
|
+
case "H":
|
|
1936
|
+
out.push({ key: "H", data: [...data] });
|
|
1937
|
+
cx = data[0];
|
|
1938
|
+
break;
|
|
1939
|
+
case "h":
|
|
1940
|
+
cx += data[0];
|
|
1941
|
+
out.push({ key: "H", data: [cx] });
|
|
1942
|
+
break;
|
|
1943
|
+
case "V":
|
|
1944
|
+
out.push({ key: "V", data: [...data] });
|
|
1945
|
+
cy = data[0];
|
|
1946
|
+
break;
|
|
1947
|
+
case "v":
|
|
1948
|
+
cy += data[0];
|
|
1949
|
+
out.push({ key: "V", data: [cy] });
|
|
1950
|
+
break;
|
|
1951
|
+
case "S":
|
|
1952
|
+
out.push({ key: "S", data: [...data] });
|
|
1953
|
+
cx = data[2];
|
|
1954
|
+
cy = data[3];
|
|
1955
|
+
break;
|
|
1956
|
+
case "s": {
|
|
1957
|
+
const newdata = data.map((d, i) => i % 2 ? d + cy : d + cx);
|
|
1958
|
+
out.push({ key: "S", data: newdata });
|
|
1959
|
+
cx = newdata[2];
|
|
1960
|
+
cy = newdata[3];
|
|
1961
|
+
break;
|
|
1962
|
+
}
|
|
1963
|
+
case "T":
|
|
1964
|
+
out.push({ key: "T", data: [...data] });
|
|
1965
|
+
cx = data[0];
|
|
1966
|
+
cy = data[1];
|
|
1967
|
+
break;
|
|
1968
|
+
case "t":
|
|
1969
|
+
cx += data[0];
|
|
1970
|
+
cy += data[1];
|
|
1971
|
+
out.push({ key: "T", data: [cx, cy] });
|
|
1972
|
+
break;
|
|
1973
|
+
case "Z":
|
|
1974
|
+
case "z":
|
|
1975
|
+
out.push({ key: "Z", data: [] });
|
|
1976
|
+
cx = subx;
|
|
1977
|
+
cy = suby;
|
|
1978
|
+
break;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
return out;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
// ../../node_modules/.pnpm/path-data-parser@0.1.0/node_modules/path-data-parser/lib/normalize.js
|
|
1985
|
+
function normalize(segments) {
|
|
1986
|
+
const out = [];
|
|
1987
|
+
let lastType = "";
|
|
1988
|
+
let cx = 0, cy = 0;
|
|
1989
|
+
let subx = 0, suby = 0;
|
|
1990
|
+
let lcx = 0, lcy = 0;
|
|
1991
|
+
for (const { key, data } of segments) {
|
|
1992
|
+
switch (key) {
|
|
1993
|
+
case "M":
|
|
1994
|
+
out.push({ key: "M", data: [...data] });
|
|
1995
|
+
[cx, cy] = data;
|
|
1996
|
+
[subx, suby] = data;
|
|
1997
|
+
break;
|
|
1998
|
+
case "C":
|
|
1999
|
+
out.push({ key: "C", data: [...data] });
|
|
2000
|
+
cx = data[4];
|
|
2001
|
+
cy = data[5];
|
|
2002
|
+
lcx = data[2];
|
|
2003
|
+
lcy = data[3];
|
|
2004
|
+
break;
|
|
2005
|
+
case "L":
|
|
2006
|
+
out.push({ key: "L", data: [...data] });
|
|
2007
|
+
[cx, cy] = data;
|
|
2008
|
+
break;
|
|
2009
|
+
case "H":
|
|
2010
|
+
cx = data[0];
|
|
2011
|
+
out.push({ key: "L", data: [cx, cy] });
|
|
2012
|
+
break;
|
|
2013
|
+
case "V":
|
|
2014
|
+
cy = data[0];
|
|
2015
|
+
out.push({ key: "L", data: [cx, cy] });
|
|
2016
|
+
break;
|
|
2017
|
+
case "S": {
|
|
2018
|
+
let cx1 = 0, cy1 = 0;
|
|
2019
|
+
if (lastType === "C" || lastType === "S") {
|
|
2020
|
+
cx1 = cx + (cx - lcx);
|
|
2021
|
+
cy1 = cy + (cy - lcy);
|
|
2022
|
+
} else {
|
|
2023
|
+
cx1 = cx;
|
|
2024
|
+
cy1 = cy;
|
|
2025
|
+
}
|
|
2026
|
+
out.push({ key: "C", data: [cx1, cy1, ...data] });
|
|
2027
|
+
lcx = data[0];
|
|
2028
|
+
lcy = data[1];
|
|
2029
|
+
cx = data[2];
|
|
2030
|
+
cy = data[3];
|
|
2031
|
+
break;
|
|
2032
|
+
}
|
|
2033
|
+
case "T": {
|
|
2034
|
+
const [x, y] = data;
|
|
2035
|
+
let x1 = 0, y1 = 0;
|
|
2036
|
+
if (lastType === "Q" || lastType === "T") {
|
|
2037
|
+
x1 = cx + (cx - lcx);
|
|
2038
|
+
y1 = cy + (cy - lcy);
|
|
2039
|
+
} else {
|
|
2040
|
+
x1 = cx;
|
|
2041
|
+
y1 = cy;
|
|
2042
|
+
}
|
|
2043
|
+
const cx1 = cx + 2 * (x1 - cx) / 3;
|
|
2044
|
+
const cy1 = cy + 2 * (y1 - cy) / 3;
|
|
2045
|
+
const cx2 = x + 2 * (x1 - x) / 3;
|
|
2046
|
+
const cy2 = y + 2 * (y1 - y) / 3;
|
|
2047
|
+
out.push({ key: "C", data: [cx1, cy1, cx2, cy2, x, y] });
|
|
2048
|
+
lcx = x1;
|
|
2049
|
+
lcy = y1;
|
|
2050
|
+
cx = x;
|
|
2051
|
+
cy = y;
|
|
2052
|
+
break;
|
|
2053
|
+
}
|
|
2054
|
+
case "Q": {
|
|
2055
|
+
const [x1, y1, x, y] = data;
|
|
2056
|
+
const cx1 = cx + 2 * (x1 - cx) / 3;
|
|
2057
|
+
const cy1 = cy + 2 * (y1 - cy) / 3;
|
|
2058
|
+
const cx2 = x + 2 * (x1 - x) / 3;
|
|
2059
|
+
const cy2 = y + 2 * (y1 - y) / 3;
|
|
2060
|
+
out.push({ key: "C", data: [cx1, cy1, cx2, cy2, x, y] });
|
|
2061
|
+
lcx = x1;
|
|
2062
|
+
lcy = y1;
|
|
2063
|
+
cx = x;
|
|
2064
|
+
cy = y;
|
|
2065
|
+
break;
|
|
2066
|
+
}
|
|
2067
|
+
case "A": {
|
|
2068
|
+
const r1 = Math.abs(data[0]);
|
|
2069
|
+
const r22 = Math.abs(data[1]);
|
|
2070
|
+
const angle = data[2];
|
|
2071
|
+
const largeArcFlag = data[3];
|
|
2072
|
+
const sweepFlag = data[4];
|
|
2073
|
+
const x = data[5];
|
|
2074
|
+
const y = data[6];
|
|
2075
|
+
if (r1 === 0 || r22 === 0) {
|
|
2076
|
+
out.push({ key: "C", data: [cx, cy, x, y, x, y] });
|
|
2077
|
+
cx = x;
|
|
2078
|
+
cy = y;
|
|
2079
|
+
} else {
|
|
2080
|
+
if (cx !== x || cy !== y) {
|
|
2081
|
+
const curves = arcToCubicCurves(cx, cy, x, y, r1, r22, angle, largeArcFlag, sweepFlag);
|
|
2082
|
+
curves.forEach(function(curve2) {
|
|
2083
|
+
out.push({ key: "C", data: curve2 });
|
|
2084
|
+
});
|
|
2085
|
+
cx = x;
|
|
2086
|
+
cy = y;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
break;
|
|
2090
|
+
}
|
|
2091
|
+
case "Z":
|
|
2092
|
+
out.push({ key: "Z", data: [] });
|
|
2093
|
+
cx = subx;
|
|
2094
|
+
cy = suby;
|
|
2095
|
+
break;
|
|
2096
|
+
}
|
|
2097
|
+
lastType = key;
|
|
2098
|
+
}
|
|
2099
|
+
return out;
|
|
2100
|
+
}
|
|
2101
|
+
function degToRad(degrees) {
|
|
2102
|
+
return Math.PI * degrees / 180;
|
|
2103
|
+
}
|
|
2104
|
+
function rotate(x, y, angleRad) {
|
|
2105
|
+
const X = x * Math.cos(angleRad) - y * Math.sin(angleRad);
|
|
2106
|
+
const Y = x * Math.sin(angleRad) + y * Math.cos(angleRad);
|
|
2107
|
+
return [X, Y];
|
|
2108
|
+
}
|
|
2109
|
+
function arcToCubicCurves(x1, y1, x2, y2, r1, r22, angle, largeArcFlag, sweepFlag, recursive) {
|
|
2110
|
+
const angleRad = degToRad(angle);
|
|
2111
|
+
let params = [];
|
|
2112
|
+
let f1 = 0, f2 = 0, cx = 0, cy = 0;
|
|
2113
|
+
if (recursive) {
|
|
2114
|
+
[f1, f2, cx, cy] = recursive;
|
|
2115
|
+
} else {
|
|
2116
|
+
[x1, y1] = rotate(x1, y1, -angleRad);
|
|
2117
|
+
[x2, y2] = rotate(x2, y2, -angleRad);
|
|
2118
|
+
const x = (x1 - x2) / 2;
|
|
2119
|
+
const y = (y1 - y2) / 2;
|
|
2120
|
+
let h = x * x / (r1 * r1) + y * y / (r22 * r22);
|
|
2121
|
+
if (h > 1) {
|
|
2122
|
+
h = Math.sqrt(h);
|
|
2123
|
+
r1 = h * r1;
|
|
2124
|
+
r22 = h * r22;
|
|
2125
|
+
}
|
|
2126
|
+
const sign = largeArcFlag === sweepFlag ? -1 : 1;
|
|
2127
|
+
const r1Pow = r1 * r1;
|
|
2128
|
+
const r2Pow = r22 * r22;
|
|
2129
|
+
const left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x;
|
|
2130
|
+
const right = r1Pow * y * y + r2Pow * x * x;
|
|
2131
|
+
const k = sign * Math.sqrt(Math.abs(left / right));
|
|
2132
|
+
cx = k * r1 * y / r22 + (x1 + x2) / 2;
|
|
2133
|
+
cy = k * -r22 * x / r1 + (y1 + y2) / 2;
|
|
2134
|
+
f1 = Math.asin(parseFloat(((y1 - cy) / r22).toFixed(9)));
|
|
2135
|
+
f2 = Math.asin(parseFloat(((y2 - cy) / r22).toFixed(9)));
|
|
2136
|
+
if (x1 < cx) {
|
|
2137
|
+
f1 = Math.PI - f1;
|
|
2138
|
+
}
|
|
2139
|
+
if (x2 < cx) {
|
|
2140
|
+
f2 = Math.PI - f2;
|
|
2141
|
+
}
|
|
2142
|
+
if (f1 < 0) {
|
|
2143
|
+
f1 = Math.PI * 2 + f1;
|
|
2144
|
+
}
|
|
2145
|
+
if (f2 < 0) {
|
|
2146
|
+
f2 = Math.PI * 2 + f2;
|
|
2147
|
+
}
|
|
2148
|
+
if (sweepFlag && f1 > f2) {
|
|
2149
|
+
f1 = f1 - Math.PI * 2;
|
|
2150
|
+
}
|
|
2151
|
+
if (!sweepFlag && f2 > f1) {
|
|
2152
|
+
f2 = f2 - Math.PI * 2;
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
let df = f2 - f1;
|
|
2156
|
+
if (Math.abs(df) > Math.PI * 120 / 180) {
|
|
2157
|
+
const f2old = f2;
|
|
2158
|
+
const x2old = x2;
|
|
2159
|
+
const y2old = y2;
|
|
2160
|
+
if (sweepFlag && f2 > f1) {
|
|
2161
|
+
f2 = f1 + Math.PI * 120 / 180 * 1;
|
|
2162
|
+
} else {
|
|
2163
|
+
f2 = f1 + Math.PI * 120 / 180 * -1;
|
|
2164
|
+
}
|
|
2165
|
+
x2 = cx + r1 * Math.cos(f2);
|
|
2166
|
+
y2 = cy + r22 * Math.sin(f2);
|
|
2167
|
+
params = arcToCubicCurves(x2, y2, x2old, y2old, r1, r22, angle, 0, sweepFlag, [f2, f2old, cx, cy]);
|
|
2168
|
+
}
|
|
2169
|
+
df = f2 - f1;
|
|
2170
|
+
const c1 = Math.cos(f1);
|
|
2171
|
+
const s1 = Math.sin(f1);
|
|
2172
|
+
const c2 = Math.cos(f2);
|
|
2173
|
+
const s2 = Math.sin(f2);
|
|
2174
|
+
const t = Math.tan(df / 4);
|
|
2175
|
+
const hx = 4 / 3 * r1 * t;
|
|
2176
|
+
const hy = 4 / 3 * r22 * t;
|
|
2177
|
+
const m1 = [x1, y1];
|
|
2178
|
+
const m2 = [x1 + hx * s1, y1 - hy * c1];
|
|
2179
|
+
const m3 = [x2 + hx * s2, y2 - hy * c2];
|
|
2180
|
+
const m4 = [x2, y2];
|
|
2181
|
+
m2[0] = 2 * m1[0] - m2[0];
|
|
2182
|
+
m2[1] = 2 * m1[1] - m2[1];
|
|
2183
|
+
if (recursive) {
|
|
2184
|
+
return [m2, m3, m4].concat(params);
|
|
2185
|
+
} else {
|
|
2186
|
+
params = [m2, m3, m4].concat(params);
|
|
2187
|
+
const curves = [];
|
|
2188
|
+
for (let i = 0; i < params.length; i += 3) {
|
|
2189
|
+
const r12 = rotate(params[i][0], params[i][1], angleRad);
|
|
2190
|
+
const r23 = rotate(params[i + 1][0], params[i + 1][1], angleRad);
|
|
2191
|
+
const r3 = rotate(params[i + 2][0], params[i + 2][1], angleRad);
|
|
2192
|
+
curves.push([r12[0], r12[1], r23[0], r23[1], r3[0], r3[1]]);
|
|
2193
|
+
}
|
|
2194
|
+
return curves;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/renderer.js
|
|
2199
|
+
var helper = {
|
|
2200
|
+
randOffset,
|
|
2201
|
+
randOffsetWithRange,
|
|
2202
|
+
ellipse,
|
|
2203
|
+
doubleLineOps: doubleLineFillOps
|
|
2204
|
+
};
|
|
2205
|
+
function line(x1, y1, x2, y2, o) {
|
|
2206
|
+
return { type: "path", ops: _doubleLine(x1, y1, x2, y2, o) };
|
|
2207
|
+
}
|
|
2208
|
+
function linearPath(points, close, o) {
|
|
2209
|
+
const len = (points || []).length;
|
|
2210
|
+
if (len > 2) {
|
|
2211
|
+
const ops = [];
|
|
2212
|
+
for (let i = 0; i < len - 1; i++) {
|
|
2213
|
+
ops.push(..._doubleLine(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], o));
|
|
2214
|
+
}
|
|
2215
|
+
if (close) {
|
|
2216
|
+
ops.push(..._doubleLine(points[len - 1][0], points[len - 1][1], points[0][0], points[0][1], o));
|
|
2217
|
+
}
|
|
2218
|
+
return { type: "path", ops };
|
|
2219
|
+
} else if (len === 2) {
|
|
2220
|
+
return line(points[0][0], points[0][1], points[1][0], points[1][1], o);
|
|
2221
|
+
}
|
|
2222
|
+
return { type: "path", ops: [] };
|
|
2223
|
+
}
|
|
2224
|
+
function polygon(points, o) {
|
|
2225
|
+
return linearPath(points, true, o);
|
|
2226
|
+
}
|
|
2227
|
+
function rectangle(x, y, width, height, o) {
|
|
2228
|
+
const points = [
|
|
2229
|
+
[x, y],
|
|
2230
|
+
[x + width, y],
|
|
2231
|
+
[x + width, y + height],
|
|
2232
|
+
[x, y + height]
|
|
2233
|
+
];
|
|
2234
|
+
return polygon(points, o);
|
|
2235
|
+
}
|
|
2236
|
+
function curve(inputPoints, o) {
|
|
2237
|
+
if (inputPoints.length) {
|
|
2238
|
+
const p1 = inputPoints[0];
|
|
2239
|
+
const pointsList = typeof p1[0] === "number" ? [inputPoints] : inputPoints;
|
|
2240
|
+
const o1 = _curveWithOffset(pointsList[0], 1 * (1 + o.roughness * 0.2), o);
|
|
2241
|
+
const o2 = o.disableMultiStroke ? [] : _curveWithOffset(pointsList[0], 1.5 * (1 + o.roughness * 0.22), cloneOptionsAlterSeed(o));
|
|
2242
|
+
for (let i = 1; i < pointsList.length; i++) {
|
|
2243
|
+
const points = pointsList[i];
|
|
2244
|
+
if (points.length) {
|
|
2245
|
+
const underlay = _curveWithOffset(points, 1 * (1 + o.roughness * 0.2), o);
|
|
2246
|
+
const overlay = o.disableMultiStroke ? [] : _curveWithOffset(points, 1.5 * (1 + o.roughness * 0.22), cloneOptionsAlterSeed(o));
|
|
2247
|
+
for (const item of underlay) {
|
|
2248
|
+
if (item.op !== "move") {
|
|
2249
|
+
o1.push(item);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
for (const item of overlay) {
|
|
2253
|
+
if (item.op !== "move") {
|
|
2254
|
+
o2.push(item);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
return { type: "path", ops: o1.concat(o2) };
|
|
2260
|
+
}
|
|
2261
|
+
return { type: "path", ops: [] };
|
|
2262
|
+
}
|
|
2263
|
+
function ellipse(x, y, width, height, o) {
|
|
2264
|
+
const params = generateEllipseParams(width, height, o);
|
|
2265
|
+
return ellipseWithParams(x, y, o, params).opset;
|
|
2266
|
+
}
|
|
2267
|
+
function generateEllipseParams(width, height, o) {
|
|
2268
|
+
const psq = Math.sqrt(Math.PI * 2 * Math.sqrt((Math.pow(width / 2, 2) + Math.pow(height / 2, 2)) / 2));
|
|
2269
|
+
const stepCount = Math.ceil(Math.max(o.curveStepCount, o.curveStepCount / Math.sqrt(200) * psq));
|
|
2270
|
+
const increment = Math.PI * 2 / stepCount;
|
|
2271
|
+
let rx = Math.abs(width / 2);
|
|
2272
|
+
let ry = Math.abs(height / 2);
|
|
2273
|
+
const curveFitRandomness = 1 - o.curveFitting;
|
|
2274
|
+
rx += _offsetOpt(rx * curveFitRandomness, o);
|
|
2275
|
+
ry += _offsetOpt(ry * curveFitRandomness, o);
|
|
2276
|
+
return { increment, rx, ry };
|
|
2277
|
+
}
|
|
2278
|
+
function ellipseWithParams(x, y, o, ellipseParams) {
|
|
2279
|
+
const [ap1, cp1] = _computeEllipsePoints(ellipseParams.increment, x, y, ellipseParams.rx, ellipseParams.ry, 1, ellipseParams.increment * _offset(0.1, _offset(0.4, 1, o), o), o);
|
|
2280
|
+
let o1 = _curve(ap1, null, o);
|
|
2281
|
+
if (!o.disableMultiStroke && o.roughness !== 0) {
|
|
2282
|
+
const [ap2] = _computeEllipsePoints(ellipseParams.increment, x, y, ellipseParams.rx, ellipseParams.ry, 1.5, 0, o);
|
|
2283
|
+
const o2 = _curve(ap2, null, o);
|
|
2284
|
+
o1 = o1.concat(o2);
|
|
2285
|
+
}
|
|
2286
|
+
return {
|
|
2287
|
+
estimatedPoints: cp1,
|
|
2288
|
+
opset: { type: "path", ops: o1 }
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
function arc(x, y, width, height, start, stop, closed, roughClosure, o) {
|
|
2292
|
+
const cx = x;
|
|
2293
|
+
const cy = y;
|
|
2294
|
+
let rx = Math.abs(width / 2);
|
|
2295
|
+
let ry = Math.abs(height / 2);
|
|
2296
|
+
rx += _offsetOpt(rx * 0.01, o);
|
|
2297
|
+
ry += _offsetOpt(ry * 0.01, o);
|
|
2298
|
+
let strt = start;
|
|
2299
|
+
let stp = stop;
|
|
2300
|
+
while (strt < 0) {
|
|
2301
|
+
strt += Math.PI * 2;
|
|
2302
|
+
stp += Math.PI * 2;
|
|
2303
|
+
}
|
|
2304
|
+
if (stp - strt > Math.PI * 2) {
|
|
2305
|
+
strt = 0;
|
|
2306
|
+
stp = Math.PI * 2;
|
|
2307
|
+
}
|
|
2308
|
+
const ellipseInc = Math.PI * 2 / o.curveStepCount;
|
|
2309
|
+
const arcInc = Math.min(ellipseInc / 2, (stp - strt) / 2);
|
|
2310
|
+
const ops = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1, o);
|
|
2311
|
+
if (!o.disableMultiStroke) {
|
|
2312
|
+
const o2 = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1.5, o);
|
|
2313
|
+
ops.push(...o2);
|
|
2314
|
+
}
|
|
2315
|
+
if (closed) {
|
|
2316
|
+
if (roughClosure) {
|
|
2317
|
+
ops.push(..._doubleLine(cx, cy, cx + rx * Math.cos(strt), cy + ry * Math.sin(strt), o), ..._doubleLine(cx, cy, cx + rx * Math.cos(stp), cy + ry * Math.sin(stp), o));
|
|
2318
|
+
} else {
|
|
2319
|
+
ops.push({ op: "lineTo", data: [cx, cy] }, { op: "lineTo", data: [cx + rx * Math.cos(strt), cy + ry * Math.sin(strt)] });
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
return { type: "path", ops };
|
|
2323
|
+
}
|
|
2324
|
+
function svgPath(path, o) {
|
|
2325
|
+
const segments = normalize(absolutize(parsePath(path)));
|
|
2326
|
+
const ops = [];
|
|
2327
|
+
let first = [0, 0];
|
|
2328
|
+
let current = [0, 0];
|
|
2329
|
+
for (const { key, data } of segments) {
|
|
2330
|
+
switch (key) {
|
|
2331
|
+
case "M": {
|
|
2332
|
+
current = [data[0], data[1]];
|
|
2333
|
+
first = [data[0], data[1]];
|
|
2334
|
+
break;
|
|
2335
|
+
}
|
|
2336
|
+
case "L":
|
|
2337
|
+
ops.push(..._doubleLine(current[0], current[1], data[0], data[1], o));
|
|
2338
|
+
current = [data[0], data[1]];
|
|
2339
|
+
break;
|
|
2340
|
+
case "C": {
|
|
2341
|
+
const [x1, y1, x2, y2, x, y] = data;
|
|
2342
|
+
ops.push(..._bezierTo(x1, y1, x2, y2, x, y, current, o));
|
|
2343
|
+
current = [x, y];
|
|
2344
|
+
break;
|
|
2345
|
+
}
|
|
2346
|
+
case "Z":
|
|
2347
|
+
ops.push(..._doubleLine(current[0], current[1], first[0], first[1], o));
|
|
2348
|
+
current = [first[0], first[1]];
|
|
2349
|
+
break;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
return { type: "path", ops };
|
|
2353
|
+
}
|
|
2354
|
+
function solidFillPolygon(polygonList, o) {
|
|
2355
|
+
const ops = [];
|
|
2356
|
+
for (const points of polygonList) {
|
|
2357
|
+
if (points.length) {
|
|
2358
|
+
const offset = o.maxRandomnessOffset || 0;
|
|
2359
|
+
const len = points.length;
|
|
2360
|
+
if (len > 2) {
|
|
2361
|
+
ops.push({ op: "move", data: [points[0][0] + _offsetOpt(offset, o), points[0][1] + _offsetOpt(offset, o)] });
|
|
2362
|
+
for (let i = 1; i < len; i++) {
|
|
2363
|
+
ops.push({ op: "lineTo", data: [points[i][0] + _offsetOpt(offset, o), points[i][1] + _offsetOpt(offset, o)] });
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
return { type: "fillPath", ops };
|
|
2369
|
+
}
|
|
2370
|
+
function patternFillPolygons(polygonList, o) {
|
|
2371
|
+
return getFiller(o, helper).fillPolygons(polygonList, o);
|
|
2372
|
+
}
|
|
2373
|
+
function patternFillArc(x, y, width, height, start, stop, o) {
|
|
2374
|
+
const cx = x;
|
|
2375
|
+
const cy = y;
|
|
2376
|
+
let rx = Math.abs(width / 2);
|
|
2377
|
+
let ry = Math.abs(height / 2);
|
|
2378
|
+
rx += _offsetOpt(rx * 0.01, o);
|
|
2379
|
+
ry += _offsetOpt(ry * 0.01, o);
|
|
2380
|
+
let strt = start;
|
|
2381
|
+
let stp = stop;
|
|
2382
|
+
while (strt < 0) {
|
|
2383
|
+
strt += Math.PI * 2;
|
|
2384
|
+
stp += Math.PI * 2;
|
|
2385
|
+
}
|
|
2386
|
+
if (stp - strt > Math.PI * 2) {
|
|
2387
|
+
strt = 0;
|
|
2388
|
+
stp = Math.PI * 2;
|
|
2389
|
+
}
|
|
2390
|
+
const increment = (stp - strt) / o.curveStepCount;
|
|
2391
|
+
const points = [];
|
|
2392
|
+
for (let angle = strt; angle <= stp; angle = angle + increment) {
|
|
2393
|
+
points.push([cx + rx * Math.cos(angle), cy + ry * Math.sin(angle)]);
|
|
2394
|
+
}
|
|
2395
|
+
points.push([cx + rx * Math.cos(stp), cy + ry * Math.sin(stp)]);
|
|
2396
|
+
points.push([cx, cy]);
|
|
2397
|
+
return patternFillPolygons([points], o);
|
|
2398
|
+
}
|
|
2399
|
+
function randOffset(x, o) {
|
|
2400
|
+
return _offsetOpt(x, o);
|
|
2401
|
+
}
|
|
2402
|
+
function randOffsetWithRange(min, max, o) {
|
|
2403
|
+
return _offset(min, max, o);
|
|
2404
|
+
}
|
|
2405
|
+
function doubleLineFillOps(x1, y1, x2, y2, o) {
|
|
2406
|
+
return _doubleLine(x1, y1, x2, y2, o, true);
|
|
2407
|
+
}
|
|
2408
|
+
function cloneOptionsAlterSeed(ops) {
|
|
2409
|
+
const result = Object.assign({}, ops);
|
|
2410
|
+
result.randomizer = void 0;
|
|
2411
|
+
if (ops.seed) {
|
|
2412
|
+
result.seed = ops.seed + 1;
|
|
2413
|
+
}
|
|
2414
|
+
return result;
|
|
2415
|
+
}
|
|
2416
|
+
function random(ops) {
|
|
2417
|
+
if (!ops.randomizer) {
|
|
2418
|
+
ops.randomizer = new Random(ops.seed || 0);
|
|
2419
|
+
}
|
|
2420
|
+
return ops.randomizer.next();
|
|
2421
|
+
}
|
|
2422
|
+
function _offset(min, max, ops, roughnessGain = 1) {
|
|
2423
|
+
return ops.roughness * roughnessGain * (random(ops) * (max - min) + min);
|
|
2424
|
+
}
|
|
2425
|
+
function _offsetOpt(x, ops, roughnessGain = 1) {
|
|
2426
|
+
return _offset(-x, x, ops, roughnessGain);
|
|
2427
|
+
}
|
|
2428
|
+
function _doubleLine(x1, y1, x2, y2, o, filling = false) {
|
|
2429
|
+
const singleStroke = filling ? o.disableMultiStrokeFill : o.disableMultiStroke;
|
|
2430
|
+
const o1 = _line(x1, y1, x2, y2, o, true, false);
|
|
2431
|
+
if (singleStroke) {
|
|
2432
|
+
return o1;
|
|
2433
|
+
}
|
|
2434
|
+
const o2 = _line(x1, y1, x2, y2, o, true, true);
|
|
2435
|
+
return o1.concat(o2);
|
|
2436
|
+
}
|
|
2437
|
+
function _line(x1, y1, x2, y2, o, move, overlay) {
|
|
2438
|
+
const lengthSq = Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2);
|
|
2439
|
+
const length = Math.sqrt(lengthSq);
|
|
2440
|
+
let roughnessGain = 1;
|
|
2441
|
+
if (length < 200) {
|
|
2442
|
+
roughnessGain = 1;
|
|
2443
|
+
} else if (length > 500) {
|
|
2444
|
+
roughnessGain = 0.4;
|
|
2445
|
+
} else {
|
|
2446
|
+
roughnessGain = -16668e-7 * length + 1.233334;
|
|
2447
|
+
}
|
|
2448
|
+
let offset = o.maxRandomnessOffset || 0;
|
|
2449
|
+
if (offset * offset * 100 > lengthSq) {
|
|
2450
|
+
offset = length / 10;
|
|
2451
|
+
}
|
|
2452
|
+
const halfOffset = offset / 2;
|
|
2453
|
+
const divergePoint = 0.2 + random(o) * 0.2;
|
|
2454
|
+
let midDispX = o.bowing * o.maxRandomnessOffset * (y2 - y1) / 200;
|
|
2455
|
+
let midDispY = o.bowing * o.maxRandomnessOffset * (x1 - x2) / 200;
|
|
2456
|
+
midDispX = _offsetOpt(midDispX, o, roughnessGain);
|
|
2457
|
+
midDispY = _offsetOpt(midDispY, o, roughnessGain);
|
|
2458
|
+
const ops = [];
|
|
2459
|
+
const randomHalf = () => _offsetOpt(halfOffset, o, roughnessGain);
|
|
2460
|
+
const randomFull = () => _offsetOpt(offset, o, roughnessGain);
|
|
2461
|
+
const preserveVertices = o.preserveVertices;
|
|
2462
|
+
if (move) {
|
|
2463
|
+
if (overlay) {
|
|
2464
|
+
ops.push({
|
|
2465
|
+
op: "move",
|
|
2466
|
+
data: [
|
|
2467
|
+
x1 + (preserveVertices ? 0 : randomHalf()),
|
|
2468
|
+
y1 + (preserveVertices ? 0 : randomHalf())
|
|
2469
|
+
]
|
|
2470
|
+
});
|
|
2471
|
+
} else {
|
|
2472
|
+
ops.push({
|
|
2473
|
+
op: "move",
|
|
2474
|
+
data: [
|
|
2475
|
+
x1 + (preserveVertices ? 0 : _offsetOpt(offset, o, roughnessGain)),
|
|
2476
|
+
y1 + (preserveVertices ? 0 : _offsetOpt(offset, o, roughnessGain))
|
|
2477
|
+
]
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
if (overlay) {
|
|
2482
|
+
ops.push({
|
|
2483
|
+
op: "bcurveTo",
|
|
2484
|
+
data: [
|
|
2485
|
+
midDispX + x1 + (x2 - x1) * divergePoint + randomHalf(),
|
|
2486
|
+
midDispY + y1 + (y2 - y1) * divergePoint + randomHalf(),
|
|
2487
|
+
midDispX + x1 + 2 * (x2 - x1) * divergePoint + randomHalf(),
|
|
2488
|
+
midDispY + y1 + 2 * (y2 - y1) * divergePoint + randomHalf(),
|
|
2489
|
+
x2 + (preserveVertices ? 0 : randomHalf()),
|
|
2490
|
+
y2 + (preserveVertices ? 0 : randomHalf())
|
|
2491
|
+
]
|
|
2492
|
+
});
|
|
2493
|
+
} else {
|
|
2494
|
+
ops.push({
|
|
2495
|
+
op: "bcurveTo",
|
|
2496
|
+
data: [
|
|
2497
|
+
midDispX + x1 + (x2 - x1) * divergePoint + randomFull(),
|
|
2498
|
+
midDispY + y1 + (y2 - y1) * divergePoint + randomFull(),
|
|
2499
|
+
midDispX + x1 + 2 * (x2 - x1) * divergePoint + randomFull(),
|
|
2500
|
+
midDispY + y1 + 2 * (y2 - y1) * divergePoint + randomFull(),
|
|
2501
|
+
x2 + (preserveVertices ? 0 : randomFull()),
|
|
2502
|
+
y2 + (preserveVertices ? 0 : randomFull())
|
|
2503
|
+
]
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
return ops;
|
|
2507
|
+
}
|
|
2508
|
+
function _curveWithOffset(points, offset, o) {
|
|
2509
|
+
if (!points.length) {
|
|
2510
|
+
return [];
|
|
2511
|
+
}
|
|
2512
|
+
const ps = [];
|
|
2513
|
+
ps.push([
|
|
2514
|
+
points[0][0] + _offsetOpt(offset, o),
|
|
2515
|
+
points[0][1] + _offsetOpt(offset, o)
|
|
2516
|
+
]);
|
|
2517
|
+
ps.push([
|
|
2518
|
+
points[0][0] + _offsetOpt(offset, o),
|
|
2519
|
+
points[0][1] + _offsetOpt(offset, o)
|
|
2520
|
+
]);
|
|
2521
|
+
for (let i = 1; i < points.length; i++) {
|
|
2522
|
+
ps.push([
|
|
2523
|
+
points[i][0] + _offsetOpt(offset, o),
|
|
2524
|
+
points[i][1] + _offsetOpt(offset, o)
|
|
2525
|
+
]);
|
|
2526
|
+
if (i === points.length - 1) {
|
|
2527
|
+
ps.push([
|
|
2528
|
+
points[i][0] + _offsetOpt(offset, o),
|
|
2529
|
+
points[i][1] + _offsetOpt(offset, o)
|
|
2530
|
+
]);
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
return _curve(ps, null, o);
|
|
2534
|
+
}
|
|
2535
|
+
function _curve(points, closePoint, o) {
|
|
2536
|
+
const len = points.length;
|
|
2537
|
+
const ops = [];
|
|
2538
|
+
if (len > 3) {
|
|
2539
|
+
const b2 = [];
|
|
2540
|
+
const s = 1 - o.curveTightness;
|
|
2541
|
+
ops.push({ op: "move", data: [points[1][0], points[1][1]] });
|
|
2542
|
+
for (let i = 1; i + 2 < len; i++) {
|
|
2543
|
+
const cachedVertArray = points[i];
|
|
2544
|
+
b2[0] = [cachedVertArray[0], cachedVertArray[1]];
|
|
2545
|
+
b2[1] = [cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6, cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6];
|
|
2546
|
+
b2[2] = [points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6, points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6];
|
|
2547
|
+
b2[3] = [points[i + 1][0], points[i + 1][1]];
|
|
2548
|
+
ops.push({ op: "bcurveTo", data: [b2[1][0], b2[1][1], b2[2][0], b2[2][1], b2[3][0], b2[3][1]] });
|
|
2549
|
+
}
|
|
2550
|
+
if (closePoint && closePoint.length === 2) {
|
|
2551
|
+
const ro = o.maxRandomnessOffset;
|
|
2552
|
+
ops.push({ op: "lineTo", data: [closePoint[0] + _offsetOpt(ro, o), closePoint[1] + _offsetOpt(ro, o)] });
|
|
2553
|
+
}
|
|
2554
|
+
} else if (len === 3) {
|
|
2555
|
+
ops.push({ op: "move", data: [points[1][0], points[1][1]] });
|
|
2556
|
+
ops.push({
|
|
2557
|
+
op: "bcurveTo",
|
|
2558
|
+
data: [
|
|
2559
|
+
points[1][0],
|
|
2560
|
+
points[1][1],
|
|
2561
|
+
points[2][0],
|
|
2562
|
+
points[2][1],
|
|
2563
|
+
points[2][0],
|
|
2564
|
+
points[2][1]
|
|
2565
|
+
]
|
|
2566
|
+
});
|
|
2567
|
+
} else if (len === 2) {
|
|
2568
|
+
ops.push(..._line(points[0][0], points[0][1], points[1][0], points[1][1], o, true, true));
|
|
2569
|
+
}
|
|
2570
|
+
return ops;
|
|
2571
|
+
}
|
|
2572
|
+
function _computeEllipsePoints(increment, cx, cy, rx, ry, offset, overlap, o) {
|
|
2573
|
+
const coreOnly = o.roughness === 0;
|
|
2574
|
+
const corePoints = [];
|
|
2575
|
+
const allPoints = [];
|
|
2576
|
+
if (coreOnly) {
|
|
2577
|
+
increment = increment / 4;
|
|
2578
|
+
allPoints.push([
|
|
2579
|
+
cx + rx * Math.cos(-increment),
|
|
2580
|
+
cy + ry * Math.sin(-increment)
|
|
2581
|
+
]);
|
|
2582
|
+
for (let angle = 0; angle <= Math.PI * 2; angle = angle + increment) {
|
|
2583
|
+
const p = [
|
|
2584
|
+
cx + rx * Math.cos(angle),
|
|
2585
|
+
cy + ry * Math.sin(angle)
|
|
2586
|
+
];
|
|
2587
|
+
corePoints.push(p);
|
|
2588
|
+
allPoints.push(p);
|
|
2589
|
+
}
|
|
2590
|
+
allPoints.push([
|
|
2591
|
+
cx + rx * Math.cos(0),
|
|
2592
|
+
cy + ry * Math.sin(0)
|
|
2593
|
+
]);
|
|
2594
|
+
allPoints.push([
|
|
2595
|
+
cx + rx * Math.cos(increment),
|
|
2596
|
+
cy + ry * Math.sin(increment)
|
|
2597
|
+
]);
|
|
2598
|
+
} else {
|
|
2599
|
+
const radOffset = _offsetOpt(0.5, o) - Math.PI / 2;
|
|
2600
|
+
allPoints.push([
|
|
2601
|
+
_offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
|
|
2602
|
+
_offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment)
|
|
2603
|
+
]);
|
|
2604
|
+
const endAngle = Math.PI * 2 + radOffset - 0.01;
|
|
2605
|
+
for (let angle = radOffset; angle < endAngle; angle = angle + increment) {
|
|
2606
|
+
const p = [
|
|
2607
|
+
_offsetOpt(offset, o) + cx + rx * Math.cos(angle),
|
|
2608
|
+
_offsetOpt(offset, o) + cy + ry * Math.sin(angle)
|
|
2609
|
+
];
|
|
2610
|
+
corePoints.push(p);
|
|
2611
|
+
allPoints.push(p);
|
|
2612
|
+
}
|
|
2613
|
+
allPoints.push([
|
|
2614
|
+
_offsetOpt(offset, o) + cx + rx * Math.cos(radOffset + Math.PI * 2 + overlap * 0.5),
|
|
2615
|
+
_offsetOpt(offset, o) + cy + ry * Math.sin(radOffset + Math.PI * 2 + overlap * 0.5)
|
|
2616
|
+
]);
|
|
2617
|
+
allPoints.push([
|
|
2618
|
+
_offsetOpt(offset, o) + cx + 0.98 * rx * Math.cos(radOffset + overlap),
|
|
2619
|
+
_offsetOpt(offset, o) + cy + 0.98 * ry * Math.sin(radOffset + overlap)
|
|
2620
|
+
]);
|
|
2621
|
+
allPoints.push([
|
|
2622
|
+
_offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset + overlap * 0.5),
|
|
2623
|
+
_offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset + overlap * 0.5)
|
|
2624
|
+
]);
|
|
2625
|
+
}
|
|
2626
|
+
return [allPoints, corePoints];
|
|
2627
|
+
}
|
|
2628
|
+
function _arc(increment, cx, cy, rx, ry, strt, stp, offset, o) {
|
|
2629
|
+
const radOffset = strt + _offsetOpt(0.1, o);
|
|
2630
|
+
const points = [];
|
|
2631
|
+
points.push([
|
|
2632
|
+
_offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
|
|
2633
|
+
_offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment)
|
|
2634
|
+
]);
|
|
2635
|
+
for (let angle = radOffset; angle <= stp; angle = angle + increment) {
|
|
2636
|
+
points.push([
|
|
2637
|
+
_offsetOpt(offset, o) + cx + rx * Math.cos(angle),
|
|
2638
|
+
_offsetOpt(offset, o) + cy + ry * Math.sin(angle)
|
|
2639
|
+
]);
|
|
2640
|
+
}
|
|
2641
|
+
points.push([
|
|
2642
|
+
cx + rx * Math.cos(stp),
|
|
2643
|
+
cy + ry * Math.sin(stp)
|
|
2644
|
+
]);
|
|
2645
|
+
points.push([
|
|
2646
|
+
cx + rx * Math.cos(stp),
|
|
2647
|
+
cy + ry * Math.sin(stp)
|
|
2648
|
+
]);
|
|
2649
|
+
return _curve(points, null, o);
|
|
2650
|
+
}
|
|
2651
|
+
function _bezierTo(x1, y1, x2, y2, x, y, current, o) {
|
|
2652
|
+
const ops = [];
|
|
2653
|
+
const ros = [o.maxRandomnessOffset || 1, (o.maxRandomnessOffset || 1) + 0.3];
|
|
2654
|
+
let f = [0, 0];
|
|
2655
|
+
const iterations = o.disableMultiStroke ? 1 : 2;
|
|
2656
|
+
const preserveVertices = o.preserveVertices;
|
|
2657
|
+
for (let i = 0; i < iterations; i++) {
|
|
2658
|
+
if (i === 0) {
|
|
2659
|
+
ops.push({ op: "move", data: [current[0], current[1]] });
|
|
2660
|
+
} else {
|
|
2661
|
+
ops.push({ op: "move", data: [current[0] + (preserveVertices ? 0 : _offsetOpt(ros[0], o)), current[1] + (preserveVertices ? 0 : _offsetOpt(ros[0], o))] });
|
|
2662
|
+
}
|
|
2663
|
+
f = preserveVertices ? [x, y] : [x + _offsetOpt(ros[i], o), y + _offsetOpt(ros[i], o)];
|
|
2664
|
+
ops.push({
|
|
2665
|
+
op: "bcurveTo",
|
|
2666
|
+
data: [
|
|
2667
|
+
x1 + _offsetOpt(ros[i], o),
|
|
2668
|
+
y1 + _offsetOpt(ros[i], o),
|
|
2669
|
+
x2 + _offsetOpt(ros[i], o),
|
|
2670
|
+
y2 + _offsetOpt(ros[i], o),
|
|
2671
|
+
f[0],
|
|
2672
|
+
f[1]
|
|
2673
|
+
]
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
return ops;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
// ../../node_modules/.pnpm/points-on-curve@0.2.0/node_modules/points-on-curve/lib/curve-to-bezier.js
|
|
2680
|
+
function clone(p) {
|
|
2681
|
+
return [...p];
|
|
2682
|
+
}
|
|
2683
|
+
function curveToBezier(pointsIn, curveTightness = 0) {
|
|
2684
|
+
const len = pointsIn.length;
|
|
2685
|
+
if (len < 3) {
|
|
2686
|
+
throw new Error("A curve must have at least three points.");
|
|
2687
|
+
}
|
|
2688
|
+
const out = [];
|
|
2689
|
+
if (len === 3) {
|
|
2690
|
+
out.push(clone(pointsIn[0]), clone(pointsIn[1]), clone(pointsIn[2]), clone(pointsIn[2]));
|
|
2691
|
+
} else {
|
|
2692
|
+
const points = [];
|
|
2693
|
+
points.push(pointsIn[0], pointsIn[0]);
|
|
2694
|
+
for (let i = 1; i < pointsIn.length; i++) {
|
|
2695
|
+
points.push(pointsIn[i]);
|
|
2696
|
+
if (i === pointsIn.length - 1) {
|
|
2697
|
+
points.push(pointsIn[i]);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
const b2 = [];
|
|
2701
|
+
const s = 1 - curveTightness;
|
|
2702
|
+
out.push(clone(points[0]));
|
|
2703
|
+
for (let i = 1; i + 2 < points.length; i++) {
|
|
2704
|
+
const cachedVertArray = points[i];
|
|
2705
|
+
b2[0] = [cachedVertArray[0], cachedVertArray[1]];
|
|
2706
|
+
b2[1] = [cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6, cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6];
|
|
2707
|
+
b2[2] = [points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6, points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6];
|
|
2708
|
+
b2[3] = [points[i + 1][0], points[i + 1][1]];
|
|
2709
|
+
out.push(b2[1], b2[2], b2[3]);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
return out;
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
// ../../node_modules/.pnpm/points-on-curve@0.2.0/node_modules/points-on-curve/lib/index.js
|
|
2716
|
+
function distance(p1, p2) {
|
|
2717
|
+
return Math.sqrt(distanceSq(p1, p2));
|
|
2718
|
+
}
|
|
2719
|
+
function distanceSq(p1, p2) {
|
|
2720
|
+
return Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2);
|
|
2721
|
+
}
|
|
2722
|
+
function distanceToSegmentSq(p, v, w) {
|
|
2723
|
+
const l2 = distanceSq(v, w);
|
|
2724
|
+
if (l2 === 0) {
|
|
2725
|
+
return distanceSq(p, v);
|
|
2726
|
+
}
|
|
2727
|
+
let t = ((p[0] - v[0]) * (w[0] - v[0]) + (p[1] - v[1]) * (w[1] - v[1])) / l2;
|
|
2728
|
+
t = Math.max(0, Math.min(1, t));
|
|
2729
|
+
return distanceSq(p, lerp(v, w, t));
|
|
2730
|
+
}
|
|
2731
|
+
function lerp(a2, b2, t) {
|
|
2732
|
+
return [
|
|
2733
|
+
a2[0] + (b2[0] - a2[0]) * t,
|
|
2734
|
+
a2[1] + (b2[1] - a2[1]) * t
|
|
2735
|
+
];
|
|
2736
|
+
}
|
|
2737
|
+
function flatness(points, offset) {
|
|
2738
|
+
const p1 = points[offset + 0];
|
|
2739
|
+
const p2 = points[offset + 1];
|
|
2740
|
+
const p3 = points[offset + 2];
|
|
2741
|
+
const p4 = points[offset + 3];
|
|
2742
|
+
let ux = 3 * p2[0] - 2 * p1[0] - p4[0];
|
|
2743
|
+
ux *= ux;
|
|
2744
|
+
let uy = 3 * p2[1] - 2 * p1[1] - p4[1];
|
|
2745
|
+
uy *= uy;
|
|
2746
|
+
let vx = 3 * p3[0] - 2 * p4[0] - p1[0];
|
|
2747
|
+
vx *= vx;
|
|
2748
|
+
let vy = 3 * p3[1] - 2 * p4[1] - p1[1];
|
|
2749
|
+
vy *= vy;
|
|
2750
|
+
if (ux < vx) {
|
|
2751
|
+
ux = vx;
|
|
2752
|
+
}
|
|
2753
|
+
if (uy < vy) {
|
|
2754
|
+
uy = vy;
|
|
2755
|
+
}
|
|
2756
|
+
return ux + uy;
|
|
2757
|
+
}
|
|
2758
|
+
function getPointsOnBezierCurveWithSplitting(points, offset, tolerance, newPoints) {
|
|
2759
|
+
const outPoints = newPoints || [];
|
|
2760
|
+
if (flatness(points, offset) < tolerance) {
|
|
2761
|
+
const p0 = points[offset + 0];
|
|
2762
|
+
if (outPoints.length) {
|
|
2763
|
+
const d = distance(outPoints[outPoints.length - 1], p0);
|
|
2764
|
+
if (d > 1) {
|
|
2765
|
+
outPoints.push(p0);
|
|
2766
|
+
}
|
|
2767
|
+
} else {
|
|
2768
|
+
outPoints.push(p0);
|
|
2769
|
+
}
|
|
2770
|
+
outPoints.push(points[offset + 3]);
|
|
2771
|
+
} else {
|
|
2772
|
+
const t = 0.5;
|
|
2773
|
+
const p1 = points[offset + 0];
|
|
2774
|
+
const p2 = points[offset + 1];
|
|
2775
|
+
const p3 = points[offset + 2];
|
|
2776
|
+
const p4 = points[offset + 3];
|
|
2777
|
+
const q1 = lerp(p1, p2, t);
|
|
2778
|
+
const q2 = lerp(p2, p3, t);
|
|
2779
|
+
const q3 = lerp(p3, p4, t);
|
|
2780
|
+
const r1 = lerp(q1, q2, t);
|
|
2781
|
+
const r22 = lerp(q2, q3, t);
|
|
2782
|
+
const red = lerp(r1, r22, t);
|
|
2783
|
+
getPointsOnBezierCurveWithSplitting([p1, q1, r1, red], 0, tolerance, outPoints);
|
|
2784
|
+
getPointsOnBezierCurveWithSplitting([red, r22, q3, p4], 0, tolerance, outPoints);
|
|
2785
|
+
}
|
|
2786
|
+
return outPoints;
|
|
2787
|
+
}
|
|
2788
|
+
function simplify(points, distance2) {
|
|
2789
|
+
return simplifyPoints(points, 0, points.length, distance2);
|
|
2790
|
+
}
|
|
2791
|
+
function simplifyPoints(points, start, end, epsilon, newPoints) {
|
|
2792
|
+
const outPoints = newPoints || [];
|
|
2793
|
+
const s = points[start];
|
|
2794
|
+
const e = points[end - 1];
|
|
2795
|
+
let maxDistSq = 0;
|
|
2796
|
+
let maxNdx = 1;
|
|
2797
|
+
for (let i = start + 1; i < end - 1; ++i) {
|
|
2798
|
+
const distSq = distanceToSegmentSq(points[i], s, e);
|
|
2799
|
+
if (distSq > maxDistSq) {
|
|
2800
|
+
maxDistSq = distSq;
|
|
2801
|
+
maxNdx = i;
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
if (Math.sqrt(maxDistSq) > epsilon) {
|
|
2805
|
+
simplifyPoints(points, start, maxNdx + 1, epsilon, outPoints);
|
|
2806
|
+
simplifyPoints(points, maxNdx, end, epsilon, outPoints);
|
|
2807
|
+
} else {
|
|
2808
|
+
if (!outPoints.length) {
|
|
2809
|
+
outPoints.push(s);
|
|
2810
|
+
}
|
|
2811
|
+
outPoints.push(e);
|
|
2812
|
+
}
|
|
2813
|
+
return outPoints;
|
|
2814
|
+
}
|
|
2815
|
+
function pointsOnBezierCurves(points, tolerance = 0.15, distance2) {
|
|
2816
|
+
const newPoints = [];
|
|
2817
|
+
const numSegments = (points.length - 1) / 3;
|
|
2818
|
+
for (let i = 0; i < numSegments; i++) {
|
|
2819
|
+
const offset = i * 3;
|
|
2820
|
+
getPointsOnBezierCurveWithSplitting(points, offset, tolerance, newPoints);
|
|
2821
|
+
}
|
|
2822
|
+
if (distance2 && distance2 > 0) {
|
|
2823
|
+
return simplifyPoints(newPoints, 0, newPoints.length, distance2);
|
|
2824
|
+
}
|
|
2825
|
+
return newPoints;
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
// ../../node_modules/.pnpm/points-on-path@0.2.1/node_modules/points-on-path/lib/index.js
|
|
2829
|
+
function pointsOnPath(path, tolerance, distance2) {
|
|
2830
|
+
const segments = parsePath(path);
|
|
2831
|
+
const normalized = normalize(absolutize(segments));
|
|
2832
|
+
const sets = [];
|
|
2833
|
+
let currentPoints = [];
|
|
2834
|
+
let start = [0, 0];
|
|
2835
|
+
let pendingCurve = [];
|
|
2836
|
+
const appendPendingCurve = () => {
|
|
2837
|
+
if (pendingCurve.length >= 4) {
|
|
2838
|
+
currentPoints.push(...pointsOnBezierCurves(pendingCurve, tolerance));
|
|
2839
|
+
}
|
|
2840
|
+
pendingCurve = [];
|
|
2841
|
+
};
|
|
2842
|
+
const appendPendingPoints = () => {
|
|
2843
|
+
appendPendingCurve();
|
|
2844
|
+
if (currentPoints.length) {
|
|
2845
|
+
sets.push(currentPoints);
|
|
2846
|
+
currentPoints = [];
|
|
2847
|
+
}
|
|
2848
|
+
};
|
|
2849
|
+
for (const { key, data } of normalized) {
|
|
2850
|
+
switch (key) {
|
|
2851
|
+
case "M":
|
|
2852
|
+
appendPendingPoints();
|
|
2853
|
+
start = [data[0], data[1]];
|
|
2854
|
+
currentPoints.push(start);
|
|
2855
|
+
break;
|
|
2856
|
+
case "L":
|
|
2857
|
+
appendPendingCurve();
|
|
2858
|
+
currentPoints.push([data[0], data[1]]);
|
|
2859
|
+
break;
|
|
2860
|
+
case "C":
|
|
2861
|
+
if (!pendingCurve.length) {
|
|
2862
|
+
const lastPoint = currentPoints.length ? currentPoints[currentPoints.length - 1] : start;
|
|
2863
|
+
pendingCurve.push([lastPoint[0], lastPoint[1]]);
|
|
2864
|
+
}
|
|
2865
|
+
pendingCurve.push([data[0], data[1]]);
|
|
2866
|
+
pendingCurve.push([data[2], data[3]]);
|
|
2867
|
+
pendingCurve.push([data[4], data[5]]);
|
|
2868
|
+
break;
|
|
2869
|
+
case "Z":
|
|
2870
|
+
appendPendingCurve();
|
|
2871
|
+
currentPoints.push([start[0], start[1]]);
|
|
2872
|
+
break;
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
appendPendingPoints();
|
|
2876
|
+
if (!distance2) {
|
|
2877
|
+
return sets;
|
|
2878
|
+
}
|
|
2879
|
+
const out = [];
|
|
2880
|
+
for (const set of sets) {
|
|
2881
|
+
const simplifiedSet = simplify(set, distance2);
|
|
2882
|
+
if (simplifiedSet.length) {
|
|
2883
|
+
out.push(simplifiedSet);
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
return out;
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
// ../../node_modules/.pnpm/roughjs@4.6.6/node_modules/roughjs/bin/generator.js
|
|
2890
|
+
var NOS = "none";
|
|
2891
|
+
var RoughGenerator = class {
|
|
2892
|
+
constructor(config) {
|
|
2893
|
+
this.defaultOptions = {
|
|
2894
|
+
maxRandomnessOffset: 2,
|
|
2895
|
+
roughness: 1,
|
|
2896
|
+
bowing: 1,
|
|
2897
|
+
stroke: "#000",
|
|
2898
|
+
strokeWidth: 1,
|
|
2899
|
+
curveTightness: 0,
|
|
2900
|
+
curveFitting: 0.95,
|
|
2901
|
+
curveStepCount: 9,
|
|
2902
|
+
fillStyle: "hachure",
|
|
2903
|
+
fillWeight: -1,
|
|
2904
|
+
hachureAngle: -41,
|
|
2905
|
+
hachureGap: -1,
|
|
2906
|
+
dashOffset: -1,
|
|
2907
|
+
dashGap: -1,
|
|
2908
|
+
zigzagOffset: -1,
|
|
2909
|
+
seed: 0,
|
|
2910
|
+
disableMultiStroke: false,
|
|
2911
|
+
disableMultiStrokeFill: false,
|
|
2912
|
+
preserveVertices: false,
|
|
2913
|
+
fillShapeRoughnessGain: 0.8
|
|
2914
|
+
};
|
|
2915
|
+
this.config = config || {};
|
|
2916
|
+
if (this.config.options) {
|
|
2917
|
+
this.defaultOptions = this._o(this.config.options);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
static newSeed() {
|
|
2921
|
+
return randomSeed();
|
|
2922
|
+
}
|
|
2923
|
+
_o(options) {
|
|
2924
|
+
return options ? Object.assign({}, this.defaultOptions, options) : this.defaultOptions;
|
|
2925
|
+
}
|
|
2926
|
+
_d(shape2, sets, options) {
|
|
2927
|
+
return { shape: shape2, sets: sets || [], options: options || this.defaultOptions };
|
|
2928
|
+
}
|
|
2929
|
+
line(x1, y1, x2, y2, options) {
|
|
2930
|
+
const o = this._o(options);
|
|
2931
|
+
return this._d("line", [line(x1, y1, x2, y2, o)], o);
|
|
2932
|
+
}
|
|
2933
|
+
rectangle(x, y, width, height, options) {
|
|
2934
|
+
const o = this._o(options);
|
|
2935
|
+
const paths = [];
|
|
2936
|
+
const outline = rectangle(x, y, width, height, o);
|
|
2937
|
+
if (o.fill) {
|
|
2938
|
+
const points = [[x, y], [x + width, y], [x + width, y + height], [x, y + height]];
|
|
2939
|
+
if (o.fillStyle === "solid") {
|
|
2940
|
+
paths.push(solidFillPolygon([points], o));
|
|
2941
|
+
} else {
|
|
2942
|
+
paths.push(patternFillPolygons([points], o));
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
if (o.stroke !== NOS) {
|
|
2946
|
+
paths.push(outline);
|
|
2947
|
+
}
|
|
2948
|
+
return this._d("rectangle", paths, o);
|
|
2949
|
+
}
|
|
2950
|
+
ellipse(x, y, width, height, options) {
|
|
2951
|
+
const o = this._o(options);
|
|
2952
|
+
const paths = [];
|
|
2953
|
+
const ellipseParams = generateEllipseParams(width, height, o);
|
|
2954
|
+
const ellipseResponse = ellipseWithParams(x, y, o, ellipseParams);
|
|
2955
|
+
if (o.fill) {
|
|
2956
|
+
if (o.fillStyle === "solid") {
|
|
2957
|
+
const shape2 = ellipseWithParams(x, y, o, ellipseParams).opset;
|
|
2958
|
+
shape2.type = "fillPath";
|
|
2959
|
+
paths.push(shape2);
|
|
2960
|
+
} else {
|
|
2961
|
+
paths.push(patternFillPolygons([ellipseResponse.estimatedPoints], o));
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
if (o.stroke !== NOS) {
|
|
2965
|
+
paths.push(ellipseResponse.opset);
|
|
2966
|
+
}
|
|
2967
|
+
return this._d("ellipse", paths, o);
|
|
2968
|
+
}
|
|
2969
|
+
circle(x, y, diameter, options) {
|
|
2970
|
+
const ret = this.ellipse(x, y, diameter, diameter, options);
|
|
2971
|
+
ret.shape = "circle";
|
|
2972
|
+
return ret;
|
|
2973
|
+
}
|
|
2974
|
+
linearPath(points, options) {
|
|
2975
|
+
const o = this._o(options);
|
|
2976
|
+
return this._d("linearPath", [linearPath(points, false, o)], o);
|
|
2977
|
+
}
|
|
2978
|
+
arc(x, y, width, height, start, stop, closed = false, options) {
|
|
2979
|
+
const o = this._o(options);
|
|
2980
|
+
const paths = [];
|
|
2981
|
+
const outline = arc(x, y, width, height, start, stop, closed, true, o);
|
|
2982
|
+
if (closed && o.fill) {
|
|
2983
|
+
if (o.fillStyle === "solid") {
|
|
2984
|
+
const fillOptions = Object.assign({}, o);
|
|
2985
|
+
fillOptions.disableMultiStroke = true;
|
|
2986
|
+
const shape2 = arc(x, y, width, height, start, stop, true, false, fillOptions);
|
|
2987
|
+
shape2.type = "fillPath";
|
|
2988
|
+
paths.push(shape2);
|
|
2989
|
+
} else {
|
|
2990
|
+
paths.push(patternFillArc(x, y, width, height, start, stop, o));
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
if (o.stroke !== NOS) {
|
|
2994
|
+
paths.push(outline);
|
|
2995
|
+
}
|
|
2996
|
+
return this._d("arc", paths, o);
|
|
2997
|
+
}
|
|
2998
|
+
curve(points, options) {
|
|
2999
|
+
const o = this._o(options);
|
|
3000
|
+
const paths = [];
|
|
3001
|
+
const outline = curve(points, o);
|
|
3002
|
+
if (o.fill && o.fill !== NOS) {
|
|
3003
|
+
if (o.fillStyle === "solid") {
|
|
3004
|
+
const fillShape = curve(points, Object.assign(Object.assign({}, o), { disableMultiStroke: true, roughness: o.roughness ? o.roughness + o.fillShapeRoughnessGain : 0 }));
|
|
3005
|
+
paths.push({
|
|
3006
|
+
type: "fillPath",
|
|
3007
|
+
ops: this._mergedShape(fillShape.ops)
|
|
3008
|
+
});
|
|
3009
|
+
} else {
|
|
3010
|
+
const polyPoints = [];
|
|
3011
|
+
const inputPoints = points;
|
|
3012
|
+
if (inputPoints.length) {
|
|
3013
|
+
const p1 = inputPoints[0];
|
|
3014
|
+
const pointsList = typeof p1[0] === "number" ? [inputPoints] : inputPoints;
|
|
3015
|
+
for (const points2 of pointsList) {
|
|
3016
|
+
if (points2.length < 3) {
|
|
3017
|
+
polyPoints.push(...points2);
|
|
3018
|
+
} else if (points2.length === 3) {
|
|
3019
|
+
polyPoints.push(...pointsOnBezierCurves(curveToBezier([
|
|
3020
|
+
points2[0],
|
|
3021
|
+
points2[0],
|
|
3022
|
+
points2[1],
|
|
3023
|
+
points2[2]
|
|
3024
|
+
]), 10, (1 + o.roughness) / 2));
|
|
3025
|
+
} else {
|
|
3026
|
+
polyPoints.push(...pointsOnBezierCurves(curveToBezier(points2), 10, (1 + o.roughness) / 2));
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
if (polyPoints.length) {
|
|
3031
|
+
paths.push(patternFillPolygons([polyPoints], o));
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
if (o.stroke !== NOS) {
|
|
3036
|
+
paths.push(outline);
|
|
3037
|
+
}
|
|
3038
|
+
return this._d("curve", paths, o);
|
|
3039
|
+
}
|
|
3040
|
+
polygon(points, options) {
|
|
3041
|
+
const o = this._o(options);
|
|
3042
|
+
const paths = [];
|
|
3043
|
+
const outline = linearPath(points, true, o);
|
|
3044
|
+
if (o.fill) {
|
|
3045
|
+
if (o.fillStyle === "solid") {
|
|
3046
|
+
paths.push(solidFillPolygon([points], o));
|
|
3047
|
+
} else {
|
|
3048
|
+
paths.push(patternFillPolygons([points], o));
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
if (o.stroke !== NOS) {
|
|
3052
|
+
paths.push(outline);
|
|
3053
|
+
}
|
|
3054
|
+
return this._d("polygon", paths, o);
|
|
3055
|
+
}
|
|
3056
|
+
path(d, options) {
|
|
3057
|
+
const o = this._o(options);
|
|
3058
|
+
const paths = [];
|
|
3059
|
+
if (!d) {
|
|
3060
|
+
return this._d("path", paths, o);
|
|
3061
|
+
}
|
|
3062
|
+
d = (d || "").replace(/\n/g, " ").replace(/(-\s)/g, "-").replace("/(ss)/g", " ");
|
|
3063
|
+
const hasFill = o.fill && o.fill !== "transparent" && o.fill !== NOS;
|
|
3064
|
+
const hasStroke = o.stroke !== NOS;
|
|
3065
|
+
const simplified = !!(o.simplification && o.simplification < 1);
|
|
3066
|
+
const distance2 = simplified ? 4 - 4 * (o.simplification || 1) : (1 + o.roughness) / 2;
|
|
3067
|
+
const sets = pointsOnPath(d, 1, distance2);
|
|
3068
|
+
const shape2 = svgPath(d, o);
|
|
3069
|
+
if (hasFill) {
|
|
3070
|
+
if (o.fillStyle === "solid") {
|
|
3071
|
+
if (sets.length === 1) {
|
|
3072
|
+
const fillShape = svgPath(d, Object.assign(Object.assign({}, o), { disableMultiStroke: true, roughness: o.roughness ? o.roughness + o.fillShapeRoughnessGain : 0 }));
|
|
3073
|
+
paths.push({
|
|
3074
|
+
type: "fillPath",
|
|
3075
|
+
ops: this._mergedShape(fillShape.ops)
|
|
3076
|
+
});
|
|
3077
|
+
} else {
|
|
3078
|
+
paths.push(solidFillPolygon(sets, o));
|
|
3079
|
+
}
|
|
3080
|
+
} else {
|
|
3081
|
+
paths.push(patternFillPolygons(sets, o));
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
if (hasStroke) {
|
|
3085
|
+
if (simplified) {
|
|
3086
|
+
sets.forEach((set) => {
|
|
3087
|
+
paths.push(linearPath(set, false, o));
|
|
3088
|
+
});
|
|
3089
|
+
} else {
|
|
3090
|
+
paths.push(shape2);
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
return this._d("path", paths, o);
|
|
3094
|
+
}
|
|
3095
|
+
opsToPath(drawing, fixedDecimals) {
|
|
3096
|
+
let path = "";
|
|
3097
|
+
for (const item of drawing.ops) {
|
|
3098
|
+
const data = typeof fixedDecimals === "number" && fixedDecimals >= 0 ? item.data.map((d) => +d.toFixed(fixedDecimals)) : item.data;
|
|
3099
|
+
switch (item.op) {
|
|
3100
|
+
case "move":
|
|
3101
|
+
path += `M${data[0]} ${data[1]} `;
|
|
3102
|
+
break;
|
|
3103
|
+
case "bcurveTo":
|
|
3104
|
+
path += `C${data[0]} ${data[1]}, ${data[2]} ${data[3]}, ${data[4]} ${data[5]} `;
|
|
3105
|
+
break;
|
|
3106
|
+
case "lineTo":
|
|
3107
|
+
path += `L${data[0]} ${data[1]} `;
|
|
3108
|
+
break;
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
return path.trim();
|
|
3112
|
+
}
|
|
3113
|
+
toPaths(drawable) {
|
|
3114
|
+
const sets = drawable.sets || [];
|
|
3115
|
+
const o = drawable.options || this.defaultOptions;
|
|
3116
|
+
const paths = [];
|
|
3117
|
+
for (const drawing of sets) {
|
|
3118
|
+
let path = null;
|
|
3119
|
+
switch (drawing.type) {
|
|
3120
|
+
case "path":
|
|
3121
|
+
path = {
|
|
3122
|
+
d: this.opsToPath(drawing),
|
|
3123
|
+
stroke: o.stroke,
|
|
3124
|
+
strokeWidth: o.strokeWidth,
|
|
3125
|
+
fill: NOS
|
|
3126
|
+
};
|
|
3127
|
+
break;
|
|
3128
|
+
case "fillPath":
|
|
3129
|
+
path = {
|
|
3130
|
+
d: this.opsToPath(drawing),
|
|
3131
|
+
stroke: NOS,
|
|
3132
|
+
strokeWidth: 0,
|
|
3133
|
+
fill: o.fill || NOS
|
|
3134
|
+
};
|
|
3135
|
+
break;
|
|
3136
|
+
case "fillSketch":
|
|
3137
|
+
path = this.fillSketch(drawing, o);
|
|
3138
|
+
break;
|
|
3139
|
+
}
|
|
3140
|
+
if (path) {
|
|
3141
|
+
paths.push(path);
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
return paths;
|
|
3145
|
+
}
|
|
3146
|
+
fillSketch(drawing, o) {
|
|
3147
|
+
let fweight = o.fillWeight;
|
|
3148
|
+
if (fweight < 0) {
|
|
3149
|
+
fweight = o.strokeWidth / 2;
|
|
3150
|
+
}
|
|
3151
|
+
return {
|
|
3152
|
+
d: this.opsToPath(drawing),
|
|
3153
|
+
stroke: o.fill || NOS,
|
|
3154
|
+
strokeWidth: fweight,
|
|
3155
|
+
fill: NOS
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
_mergedShape(input) {
|
|
3159
|
+
return input.filter((d, i) => {
|
|
3160
|
+
if (i === 0) {
|
|
3161
|
+
return true;
|
|
3162
|
+
}
|
|
3163
|
+
if (d.op === "move") {
|
|
3164
|
+
return false;
|
|
3165
|
+
}
|
|
3166
|
+
return true;
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
};
|
|
3170
|
+
|
|
3171
|
+
// ../../node_modules/.pnpm/perfect-freehand@1.2.0/node_modules/perfect-freehand/dist/esm/index.js
|
|
3172
|
+
function $(e, t, u, x = (h) => h) {
|
|
3173
|
+
return e * x(0.5 - t * (0.5 - u));
|
|
3174
|
+
}
|
|
3175
|
+
function se(e) {
|
|
3176
|
+
return [-e[0], -e[1]];
|
|
3177
|
+
}
|
|
3178
|
+
function l(e, t) {
|
|
3179
|
+
return [e[0] + t[0], e[1] + t[1]];
|
|
3180
|
+
}
|
|
3181
|
+
function a(e, t) {
|
|
3182
|
+
return [e[0] - t[0], e[1] - t[1]];
|
|
3183
|
+
}
|
|
3184
|
+
function b(e, t) {
|
|
3185
|
+
return [e[0] * t, e[1] * t];
|
|
3186
|
+
}
|
|
3187
|
+
function he(e, t) {
|
|
3188
|
+
return [e[0] / t, e[1] / t];
|
|
3189
|
+
}
|
|
3190
|
+
function R(e) {
|
|
3191
|
+
return [e[1], -e[0]];
|
|
3192
|
+
}
|
|
3193
|
+
function B(e, t) {
|
|
3194
|
+
return e[0] * t[0] + e[1] * t[1];
|
|
3195
|
+
}
|
|
3196
|
+
function ue(e, t) {
|
|
3197
|
+
return e[0] === t[0] && e[1] === t[1];
|
|
3198
|
+
}
|
|
3199
|
+
function ge(e) {
|
|
3200
|
+
return Math.hypot(e[0], e[1]);
|
|
3201
|
+
}
|
|
3202
|
+
function de(e) {
|
|
3203
|
+
return e[0] * e[0] + e[1] * e[1];
|
|
3204
|
+
}
|
|
3205
|
+
function A(e, t) {
|
|
3206
|
+
return de(a(e, t));
|
|
3207
|
+
}
|
|
3208
|
+
function G(e) {
|
|
3209
|
+
return he(e, ge(e));
|
|
3210
|
+
}
|
|
3211
|
+
function ie(e, t) {
|
|
3212
|
+
return Math.hypot(e[1] - t[1], e[0] - t[0]);
|
|
3213
|
+
}
|
|
3214
|
+
function L(e, t, u) {
|
|
3215
|
+
let x = Math.sin(u), h = Math.cos(u), y = e[0] - t[0], n = e[1] - t[1], f = y * h - n * x, d = y * x + n * h;
|
|
3216
|
+
return [f + t[0], d + t[1]];
|
|
3217
|
+
}
|
|
3218
|
+
function K(e, t, u) {
|
|
3219
|
+
return l(e, b(a(t, e), u));
|
|
3220
|
+
}
|
|
3221
|
+
function ee(e, t, u) {
|
|
3222
|
+
return l(e, b(t, u));
|
|
3223
|
+
}
|
|
3224
|
+
var { min: C, PI: xe } = Math;
|
|
3225
|
+
var pe = 0.275;
|
|
3226
|
+
var V = xe + 1e-4;
|
|
3227
|
+
function ce(e, t = {}) {
|
|
3228
|
+
let { size: u = 16, smoothing: x = 0.5, thinning: h = 0.5, simulatePressure: y = true, easing: n = (r) => r, start: f = {}, end: d = {}, last: D = false } = t, { cap: S = true, easing: j = (r) => r * (2 - r) } = f, { cap: q = true, easing: c = (r) => --r * r * r + 1 } = d;
|
|
3229
|
+
if (e.length === 0 || u <= 0) return [];
|
|
3230
|
+
let p = e[e.length - 1].runningLength, g = f.taper === false ? 0 : f.taper === true ? Math.max(u, p) : f.taper, T = d.taper === false ? 0 : d.taper === true ? Math.max(u, p) : d.taper, te = Math.pow(u * x, 2), _ = [], M = [], H = e.slice(0, 10).reduce((r, i) => {
|
|
3231
|
+
let o = i.pressure;
|
|
3232
|
+
if (y) {
|
|
3233
|
+
let s = C(1, i.distance / u), W2 = C(1, 1 - s);
|
|
3234
|
+
o = C(1, r + (W2 - r) * (s * pe));
|
|
3235
|
+
}
|
|
3236
|
+
return (r + o) / 2;
|
|
3237
|
+
}, e[0].pressure), m = $(u, h, e[e.length - 1].pressure, n), U, X = e[0].vector, z3 = e[0].point, F = z3, O = z3, E = F, J = false;
|
|
3238
|
+
for (let r = 0; r < e.length; r++) {
|
|
3239
|
+
let { pressure: i } = e[r], { point: o, vector: s, distance: W2, runningLength: I } = e[r];
|
|
3240
|
+
if (r < e.length - 1 && p - I < 3) continue;
|
|
3241
|
+
if (h) {
|
|
3242
|
+
if (y) {
|
|
3243
|
+
let v = C(1, W2 / u), Z = C(1, 1 - v);
|
|
3244
|
+
i = C(1, H + (Z - H) * (v * pe));
|
|
3245
|
+
}
|
|
3246
|
+
m = $(u, h, i, n);
|
|
3247
|
+
} else m = u / 2;
|
|
3248
|
+
U === void 0 && (U = m);
|
|
3249
|
+
let le = I < g ? j(I / g) : 1, fe = p - I < T ? c((p - I) / T) : 1;
|
|
3250
|
+
m = Math.max(0.01, m * Math.min(le, fe));
|
|
3251
|
+
let re = (r < e.length - 1 ? e[r + 1] : e[r]).vector, Y = r < e.length - 1 ? B(s, re) : 1, be = B(s, X) < 0 && !J, ne = Y !== null && Y < 0;
|
|
3252
|
+
if (be || ne) {
|
|
3253
|
+
let v = b(R(X), m);
|
|
3254
|
+
for (let Z = 1 / 13, w = 0; w <= 1; w += Z) O = L(a(o, v), o, V * w), _.push(O), E = L(l(o, v), o, V * -w), M.push(E);
|
|
3255
|
+
z3 = O, F = E, ne && (J = true);
|
|
3256
|
+
continue;
|
|
3257
|
+
}
|
|
3258
|
+
if (J = false, r === e.length - 1) {
|
|
3259
|
+
let v = b(R(s), m);
|
|
3260
|
+
_.push(a(o, v)), M.push(l(o, v));
|
|
3261
|
+
continue;
|
|
3262
|
+
}
|
|
3263
|
+
let oe = b(R(K(re, s, Y)), m);
|
|
3264
|
+
O = a(o, oe), (r <= 1 || A(z3, O) > te) && (_.push(O), z3 = O), E = l(o, oe), (r <= 1 || A(F, E) > te) && (M.push(E), F = E), H = i, X = s;
|
|
3265
|
+
}
|
|
3266
|
+
let P = e[0].point.slice(0, 2), k = e.length > 1 ? e[e.length - 1].point.slice(0, 2) : l(e[0].point, [1, 1]), Q = [], N = [];
|
|
3267
|
+
if (e.length === 1) {
|
|
3268
|
+
if (!(g || T) || D) {
|
|
3269
|
+
let r = ee(P, G(R(a(P, k))), -(U || m)), i = [];
|
|
3270
|
+
for (let o = 1 / 13, s = o; s <= 1; s += o) i.push(L(r, P, V * 2 * s));
|
|
3271
|
+
return i;
|
|
3272
|
+
}
|
|
3273
|
+
} else {
|
|
3274
|
+
if (!(g || T && e.length === 1)) if (S) for (let i = 1 / 13, o = i; o <= 1; o += i) {
|
|
3275
|
+
let s = L(M[0], P, V * o);
|
|
3276
|
+
Q.push(s);
|
|
3277
|
+
}
|
|
3278
|
+
else {
|
|
3279
|
+
let i = a(_[0], M[0]), o = b(i, 0.5), s = b(i, 0.51);
|
|
3280
|
+
Q.push(a(P, o), a(P, s), l(P, s), l(P, o));
|
|
3281
|
+
}
|
|
3282
|
+
let r = R(se(e[e.length - 1].vector));
|
|
3283
|
+
if (T || g && e.length === 1) N.push(k);
|
|
3284
|
+
else if (q) {
|
|
3285
|
+
let i = ee(k, r, m);
|
|
3286
|
+
for (let o = 1 / 29, s = o; s < 1; s += o) N.push(L(i, k, V * 3 * s));
|
|
3287
|
+
} else N.push(l(k, b(r, m)), l(k, b(r, m * 0.99)), a(k, b(r, m * 0.99)), a(k, b(r, m)));
|
|
3288
|
+
}
|
|
3289
|
+
return _.concat(N, M.reverse(), Q);
|
|
3290
|
+
}
|
|
3291
|
+
function me(e, t = {}) {
|
|
3292
|
+
var q;
|
|
3293
|
+
let { streamline: u = 0.5, size: x = 16, last: h = false } = t;
|
|
3294
|
+
if (e.length === 0) return [];
|
|
3295
|
+
let y = 0.15 + (1 - u) * 0.85, n = Array.isArray(e[0]) ? e : e.map(({ x: c, y: p, pressure: g = 0.5 }) => [c, p, g]);
|
|
3296
|
+
if (n.length === 2) {
|
|
3297
|
+
let c = n[1];
|
|
3298
|
+
n = n.slice(0, -1);
|
|
3299
|
+
for (let p = 1; p < 5; p++) n.push(K(n[0], c, p / 4));
|
|
3300
|
+
}
|
|
3301
|
+
n.length === 1 && (n = [...n, [...l(n[0], [1, 1]), ...n[0].slice(2)]]);
|
|
3302
|
+
let f = [{ point: [n[0][0], n[0][1]], pressure: n[0][2] >= 0 ? n[0][2] : 0.25, vector: [1, 1], distance: 0, runningLength: 0 }], d = false, D = 0, S = f[0], j = n.length - 1;
|
|
3303
|
+
for (let c = 1; c < n.length; c++) {
|
|
3304
|
+
let p = h && c === j ? n[c].slice(0, 2) : K(S.point, n[c], y);
|
|
3305
|
+
if (ue(S.point, p)) continue;
|
|
3306
|
+
let g = ie(p, S.point);
|
|
3307
|
+
if (D += g, c < j && !d) {
|
|
3308
|
+
if (D < x) continue;
|
|
3309
|
+
d = true;
|
|
3310
|
+
}
|
|
3311
|
+
S = { point: p, pressure: n[c][2] >= 0 ? n[c][2] : 0.5, vector: G(a(S.point, p)), distance: g, runningLength: D }, f.push(S);
|
|
3312
|
+
}
|
|
3313
|
+
return f[0].vector = ((q = f[1]) == null ? void 0 : q.vector) || [0, 0], f;
|
|
3314
|
+
}
|
|
3315
|
+
function ae(e, t = {}) {
|
|
3316
|
+
return ce(me(e, t), t);
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
// ../diagram/src/export/svg.ts
|
|
3320
|
+
var FONT_FAMILY = {
|
|
3321
|
+
1: "Virgil",
|
|
3322
|
+
2: "Helvetica",
|
|
3323
|
+
3: "Cascadia",
|
|
3324
|
+
5: "Excalifont",
|
|
3325
|
+
6: "Nunito",
|
|
3326
|
+
7: "Lilita One",
|
|
3327
|
+
8: "Comic Shanns",
|
|
3328
|
+
9: "Liberation Sans"
|
|
3329
|
+
};
|
|
3330
|
+
var METRICS = {
|
|
3331
|
+
1: { unitsPerEm: 1e3, ascender: 886, descender: -374 },
|
|
3332
|
+
2: { unitsPerEm: 2048, ascender: 1577, descender: -471 },
|
|
3333
|
+
3: { unitsPerEm: 2048, ascender: 1900, descender: -480 },
|
|
3334
|
+
5: { unitsPerEm: 1e3, ascender: 886, descender: -374 },
|
|
3335
|
+
6: { unitsPerEm: 1e3, ascender: 1011, descender: -353 },
|
|
3336
|
+
7: { unitsPerEm: 1e3, ascender: 923, descender: -220 },
|
|
3337
|
+
8: { unitsPerEm: 1e3, ascender: 750, descender: -250 },
|
|
3338
|
+
9: { unitsPerEm: 2048, ascender: 1854, descender: -434 }
|
|
3339
|
+
};
|
|
3340
|
+
var fontFamilyCss = (id2) => {
|
|
3341
|
+
const name = FONT_FAMILY[id2];
|
|
3342
|
+
if (!name) return "Segoe UI Emoji";
|
|
3343
|
+
return id2 === 5 ? `${name}, Xiaolai, Segoe UI Emoji` : `${name}, Segoe UI Emoji`;
|
|
3344
|
+
};
|
|
3345
|
+
function fontsUsed(elements) {
|
|
3346
|
+
const s = /* @__PURE__ */ new Set();
|
|
3347
|
+
for (const e of elements) {
|
|
3348
|
+
if (e.type === "text") s.add(e.fontFamily ?? 5);
|
|
3349
|
+
if (e.type === "frame" || e.type === "magicframe") s.add(2);
|
|
3350
|
+
}
|
|
3351
|
+
return s;
|
|
3352
|
+
}
|
|
3353
|
+
var DECIMALS = 2;
|
|
3354
|
+
var r2 = (n) => Math.round(n * 100) / 100;
|
|
3355
|
+
var rotate2 = ([x, y], [cx, cy], a2) => [
|
|
3356
|
+
(x - cx) * Math.cos(a2) - (y - cy) * Math.sin(a2) + cx,
|
|
3357
|
+
(x - cx) * Math.sin(a2) + (y - cy) * Math.cos(a2) + cy
|
|
3358
|
+
];
|
|
3359
|
+
var isLinear = (e) => e.type === "arrow" || e.type === "line";
|
|
3360
|
+
var isFrame = (e) => e.type === "frame" || e.type === "magicframe";
|
|
3361
|
+
var isTransparent = (c) => !c || c === "transparent" || c.length === 5 && c.endsWith("0") || c.length === 9 && c.endsWith("00");
|
|
3362
|
+
function localBounds(e) {
|
|
3363
|
+
if ((isLinear(e) || e.type === "freedraw") && e.points?.length) {
|
|
3364
|
+
const xs = e.points.map((p) => p[0]);
|
|
3365
|
+
const ys = e.points.map((p) => p[1]);
|
|
3366
|
+
return [Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)];
|
|
3367
|
+
}
|
|
3368
|
+
return [0, 0, e.width, e.height];
|
|
3369
|
+
}
|
|
3370
|
+
function elementBounds2(e) {
|
|
3371
|
+
const [x1, y1, x2, y2] = localBounds(e);
|
|
3372
|
+
const c = [e.x + (x1 + x2) / 2, e.y + (y1 + y2) / 2];
|
|
3373
|
+
const pts = (isLinear(e) || e.type === "freedraw") && e.points?.length ? e.points.map((p) => [e.x + p[0], e.y + p[1]]) : [
|
|
3374
|
+
[e.x + x1, e.y + y1],
|
|
3375
|
+
[e.x + x2, e.y + y1],
|
|
3376
|
+
[e.x + x2, e.y + y2],
|
|
3377
|
+
[e.x + x1, e.y + y2]
|
|
3378
|
+
];
|
|
3379
|
+
const r = e.angle ? pts.map((p) => rotate2(p, c, e.angle)) : pts;
|
|
3380
|
+
return [Math.min(...r.map((p) => p[0])), Math.min(...r.map((p) => p[1])), Math.max(...r.map((p) => p[0])), Math.max(...r.map((p) => p[1]))];
|
|
3381
|
+
}
|
|
3382
|
+
var gen = new RoughGenerator();
|
|
3383
|
+
var DEFAULT_PROPORTIONAL_RADIUS = 0.25;
|
|
3384
|
+
var DEFAULT_ADAPTIVE_RADIUS = 32;
|
|
3385
|
+
function cornerRadius(x, e) {
|
|
3386
|
+
const t = e.roundness?.type;
|
|
3387
|
+
if (t === 2 || t === 1) return x * DEFAULT_PROPORTIONAL_RADIUS;
|
|
3388
|
+
if (t === 3) {
|
|
3389
|
+
const fixed = e.roundness?.value ?? DEFAULT_ADAPTIVE_RADIUS;
|
|
3390
|
+
return x <= fixed / DEFAULT_PROPORTIONAL_RADIUS ? x * DEFAULT_PROPORTIONAL_RADIUS : fixed;
|
|
3391
|
+
}
|
|
3392
|
+
return 0;
|
|
3393
|
+
}
|
|
3394
|
+
var isPathALoop = (points) => points.length >= 3 && Math.hypot(points[0][0] - points.at(-1)[0], points[0][1] - points.at(-1)[1]) <= 8;
|
|
3395
|
+
function adjustRoughness(e) {
|
|
3396
|
+
const max = Math.max(e.width, e.height);
|
|
3397
|
+
const min = Math.min(e.width, e.height);
|
|
3398
|
+
const roundable = e.type === "rectangle" || e.type === "diamond" || isLinear(e);
|
|
3399
|
+
if (min >= 20 && max >= 50 || min >= 15 && !!e.roundness && roundable || isLinear(e) && max >= 50) return e.roughness;
|
|
3400
|
+
return Math.min(e.roughness / (max < 10 ? 3 : 2), 2.5);
|
|
3401
|
+
}
|
|
3402
|
+
function roughOptions(e, continuousPath = false) {
|
|
3403
|
+
const sw = e.strokeWidth ?? 2;
|
|
3404
|
+
const o = {
|
|
3405
|
+
seed: e.seed,
|
|
3406
|
+
strokeLineDash: e.strokeStyle === "dashed" ? [8, 8 + sw] : e.strokeStyle === "dotted" ? [1.5, 6 + sw] : void 0,
|
|
3407
|
+
disableMultiStroke: e.strokeStyle !== "solid",
|
|
3408
|
+
strokeWidth: e.strokeStyle !== "solid" ? sw + 0.5 : sw,
|
|
3409
|
+
fillWeight: sw / 2,
|
|
3410
|
+
hachureGap: sw * 4,
|
|
3411
|
+
roughness: adjustRoughness(e),
|
|
3412
|
+
stroke: e.strokeColor,
|
|
3413
|
+
preserveVertices: continuousPath || e.roughness < 2,
|
|
3414
|
+
fixedDecimalPlaceDigits: DECIMALS
|
|
3415
|
+
};
|
|
3416
|
+
if (e.type === "rectangle" || e.type === "diamond" || e.type === "ellipse") {
|
|
3417
|
+
o.fillStyle = e.fillStyle;
|
|
3418
|
+
o.fill = isTransparent(e.backgroundColor) ? void 0 : e.backgroundColor;
|
|
3419
|
+
if (e.type === "ellipse") o.curveFitting = 1;
|
|
3420
|
+
} else if ((e.type === "line" || e.type === "freedraw") && isPathALoop(e.points ?? [])) {
|
|
3421
|
+
o.fillStyle = e.fillStyle;
|
|
3422
|
+
o.fill = e.backgroundColor === "transparent" ? void 0 : e.backgroundColor;
|
|
3423
|
+
}
|
|
3424
|
+
return o;
|
|
3425
|
+
}
|
|
3426
|
+
function diamondPoints(e) {
|
|
3427
|
+
const topX = Math.floor(e.width / 2) + 1;
|
|
3428
|
+
const rightY = Math.floor(e.height / 2) + 1;
|
|
3429
|
+
return [topX, 0, e.width, rightY, topX, e.height, 0, rightY];
|
|
3430
|
+
}
|
|
3431
|
+
function elbowPath(points, radius) {
|
|
3432
|
+
const sub = [];
|
|
3433
|
+
const horizontal = (a2, b2) => Math.abs(a2[0] - b2[0]) >= Math.abs(a2[1] - b2[1]);
|
|
3434
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
3435
|
+
const [prev, p, next] = [points[i - 1], points[i], points[i + 1]];
|
|
3436
|
+
const corner = Math.min(radius, Math.hypot(p[0] - next[0], p[1] - next[1]) / 2, Math.hypot(p[0] - prev[0], p[1] - prev[1]) / 2);
|
|
3437
|
+
if (horizontal(p, prev)) sub.push(prev[0] < p[0] ? [p[0] - corner, p[1]] : [p[0] + corner, p[1]]);
|
|
3438
|
+
else sub.push(prev[1] < p[1] ? [p[0], p[1] - corner] : [p[0], p[1] + corner]);
|
|
3439
|
+
sub.push(p);
|
|
3440
|
+
if (horizontal(next, p)) sub.push(next[0] < p[0] ? [p[0] - corner, p[1]] : [p[0] + corner, p[1]]);
|
|
3441
|
+
else sub.push(next[1] < p[1] ? [p[0], p[1] - corner] : [p[0], p[1] + corner]);
|
|
3442
|
+
}
|
|
3443
|
+
const d = [`M ${points[0][0]} ${points[0][1]}`];
|
|
3444
|
+
for (let i = 0; i < sub.length; i += 3) d.push(`L ${sub[i][0]} ${sub[i][1]}`, `Q ${sub[i + 1][0]} ${sub[i + 1][1]}, ${sub[i + 2][0]} ${sub[i + 2][1]}`);
|
|
3445
|
+
d.push(`L ${points.at(-1)[0]} ${points.at(-1)[1]}`);
|
|
3446
|
+
return d.join(" ");
|
|
3447
|
+
}
|
|
3448
|
+
function arrowheadSize(a2) {
|
|
3449
|
+
if (a2 === "arrow") return 25;
|
|
3450
|
+
if (a2 === "diamond" || a2 === "diamond_outline") return 12;
|
|
3451
|
+
if (a2.startsWith("crowfoot")) return 20;
|
|
3452
|
+
return 15;
|
|
3453
|
+
}
|
|
3454
|
+
var arrowheadAngle = (a2) => a2 === "bar" ? 90 : a2 === "arrow" ? 20 : 25;
|
|
3455
|
+
function curveOps(shape2) {
|
|
3456
|
+
return (shape2.sets.find((s) => s.type === "path") ?? shape2.sets[0]).ops;
|
|
3457
|
+
}
|
|
3458
|
+
function arrowheadPoints(e, shape2, position, head) {
|
|
3459
|
+
const ops = curveOps(shape2);
|
|
3460
|
+
if (ops.length < 2) return null;
|
|
3461
|
+
const index = position === "start" ? 1 : ops.length - 1;
|
|
3462
|
+
const data = ops[index].data;
|
|
3463
|
+
if (data.length !== 6) return null;
|
|
3464
|
+
const p3 = [data[4], data[5]];
|
|
3465
|
+
const p2 = [data[2], data[3]];
|
|
3466
|
+
const p1 = [data[0], data[1]];
|
|
3467
|
+
const prev = ops[index - 1];
|
|
3468
|
+
let p0 = [0, 0];
|
|
3469
|
+
if (prev.op === "move") p0 = [prev.data[0], prev.data[1]];
|
|
3470
|
+
else if (prev.op === "bcurveTo") p0 = [prev.data[4], prev.data[5]];
|
|
3471
|
+
const eq = (t, i) => (1 - t) ** 3 * p3[i] + 3 * t * (1 - t) ** 2 * p2[i] + 3 * t ** 2 * (1 - t) * p1[i] + p0[i] * t ** 3;
|
|
3472
|
+
const [x2, y2] = position === "start" ? p0 : p3;
|
|
3473
|
+
const [x1, y1] = [eq(0.3, 0), eq(0.3, 1)];
|
|
3474
|
+
const dist = Math.hypot(x2 - x1, y2 - y1);
|
|
3475
|
+
const nx = (x2 - x1) / dist;
|
|
3476
|
+
const ny = (y2 - y1) / dist;
|
|
3477
|
+
const pts = e.points;
|
|
3478
|
+
const [cx, cy] = position === "end" ? pts.at(-1) : pts[0];
|
|
3479
|
+
const [px, py] = pts.length > 1 ? position === "end" ? pts.at(-2) : pts[1] : [0, 0];
|
|
3480
|
+
const length = Math.hypot(cx - px, cy - py);
|
|
3481
|
+
const minSize = Math.min(arrowheadSize(head), length * (head === "diamond" || head === "diamond_outline" ? 0.25 : 0.5));
|
|
3482
|
+
const xs = x2 - nx * minSize;
|
|
3483
|
+
const ys = y2 - ny * minSize;
|
|
3484
|
+
if (head === "dot" || head === "circle" || head === "circle_outline") return [x2, y2, Math.hypot(ys - y2, xs - x2) + (e.strokeWidth ?? 2) - 2];
|
|
3485
|
+
const angle = arrowheadAngle(head) * Math.PI / 180;
|
|
3486
|
+
if (head === "crowfoot_many" || head === "crowfoot_one_or_many") {
|
|
3487
|
+
const [x32, y32] = rotate2([x2, y2], [xs, ys], -angle);
|
|
3488
|
+
const [x42, y42] = rotate2([x2, y2], [xs, ys], angle);
|
|
3489
|
+
return [xs, ys, x32, y32, x42, y42];
|
|
3490
|
+
}
|
|
3491
|
+
const [x3, y3] = rotate2([xs, ys], [x2, y2], -angle);
|
|
3492
|
+
const [x4, y4] = rotate2([xs, ys], [x2, y2], angle);
|
|
3493
|
+
if (head === "diamond" || head === "diamond_outline") {
|
|
3494
|
+
let o;
|
|
3495
|
+
if (position === "start") {
|
|
3496
|
+
const [qx, qy] = pts.length > 1 ? pts[1] : [0, 0];
|
|
3497
|
+
o = rotate2([x2 + minSize * 2, y2], [x2, y2], Math.atan2(qy - y2, qx - x2));
|
|
3498
|
+
} else {
|
|
3499
|
+
const [qx, qy] = pts.length > 1 ? pts.at(-2) : [0, 0];
|
|
3500
|
+
o = rotate2([x2 - minSize * 2, y2], [x2, y2], Math.atan2(y2 - qy, x2 - qx));
|
|
3501
|
+
}
|
|
3502
|
+
return [x2, y2, x3, y3, o[0], o[1], x4, y4];
|
|
3503
|
+
}
|
|
3504
|
+
return [x2, y2, x3, y3, x4, y4];
|
|
3505
|
+
}
|
|
3506
|
+
function arrowheadShapes(e, shape2, position, head, options, bg) {
|
|
3507
|
+
const pts = arrowheadPoints(e, shape2, position, head);
|
|
3508
|
+
if (!pts) return [];
|
|
3509
|
+
const o = { ...options };
|
|
3510
|
+
switch (head) {
|
|
3511
|
+
case "dot":
|
|
3512
|
+
case "circle":
|
|
3513
|
+
case "circle_outline": {
|
|
3514
|
+
delete o.strokeLineDash;
|
|
3515
|
+
return [gen.circle(pts[0], pts[1], pts[2], { ...o, fill: head === "circle_outline" ? bg : e.strokeColor, fillStyle: "solid", stroke: e.strokeColor, roughness: Math.min(0.5, o.roughness || 0) })];
|
|
3516
|
+
}
|
|
3517
|
+
case "triangle":
|
|
3518
|
+
case "triangle_outline": {
|
|
3519
|
+
delete o.strokeLineDash;
|
|
3520
|
+
const [x, y, x2, y2, x3, y3] = pts;
|
|
3521
|
+
return [gen.polygon([[x, y], [x2, y2], [x3, y3], [x, y]], { ...o, fill: head === "triangle_outline" ? bg : e.strokeColor, fillStyle: "solid", roughness: Math.min(1, o.roughness || 0) })];
|
|
3522
|
+
}
|
|
3523
|
+
case "diamond":
|
|
3524
|
+
case "diamond_outline": {
|
|
3525
|
+
delete o.strokeLineDash;
|
|
3526
|
+
const [x, y, x2, y2, x3, y3, x4, y4] = pts;
|
|
3527
|
+
return [gen.polygon([[x, y], [x2, y2], [x3, y3], [x4, y4], [x, y]], { ...o, fill: head === "diamond_outline" ? bg : e.strokeColor, fillStyle: "solid", roughness: Math.min(1, o.roughness || 0) })];
|
|
3528
|
+
}
|
|
3529
|
+
case "crowfoot_one": {
|
|
3530
|
+
const [, , x3, y3, x4, y4] = pts;
|
|
3531
|
+
return [gen.line(x3, y3, x4, y4, o)];
|
|
3532
|
+
}
|
|
3533
|
+
default: {
|
|
3534
|
+
const [, , x3, y3, x4, y4] = pts;
|
|
3535
|
+
const [x2, y2] = [pts[0], pts[1]];
|
|
3536
|
+
if (e.strokeStyle === "dotted") o.strokeLineDash = [1.5, 6 + (e.strokeWidth ?? 2) - 1 - 1];
|
|
3537
|
+
else delete o.strokeLineDash;
|
|
3538
|
+
o.roughness = Math.min(1, o.roughness || 0);
|
|
3539
|
+
const out = [gen.line(x3, y3, x2, y2, o), gen.line(x4, y4, x2, y2, o)];
|
|
3540
|
+
if (head === "crowfoot_one_or_many") out.push(...arrowheadShapes(e, shape2, position, "crowfoot_one", options, bg));
|
|
3541
|
+
return out;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
function shapeOf(e, bg) {
|
|
3546
|
+
switch (e.type) {
|
|
3547
|
+
case "rectangle": {
|
|
3548
|
+
if (e.roundness) {
|
|
3549
|
+
const { width: w, height: h } = e;
|
|
3550
|
+
const r = cornerRadius(Math.min(w, h), e);
|
|
3551
|
+
return [gen.path(`M ${r} 0 L ${w - r} 0 Q ${w} 0, ${w} ${r} L ${w} ${h - r} Q ${w} ${h}, ${w - r} ${h} L ${r} ${h} Q 0 ${h}, 0 ${h - r} L 0 ${r} Q 0 0, ${r} 0`, roughOptions(e, true))];
|
|
3552
|
+
}
|
|
3553
|
+
return [gen.rectangle(0, 0, e.width, e.height, roughOptions(e))];
|
|
3554
|
+
}
|
|
3555
|
+
case "diamond": {
|
|
3556
|
+
const [tX, tY, rX, rY, bX, bY, lX, lY] = diamondPoints(e);
|
|
3557
|
+
if (e.roundness) {
|
|
3558
|
+
const v = cornerRadius(Math.abs(tX - lX), e);
|
|
3559
|
+
const h = cornerRadius(Math.abs(rY - tY), e);
|
|
3560
|
+
return [
|
|
3561
|
+
gen.path(
|
|
3562
|
+
`M ${tX + v} ${tY + h} L ${rX - v} ${rY - h} C ${rX} ${rY}, ${rX} ${rY}, ${rX - v} ${rY + h} L ${bX + v} ${bY - h} C ${bX} ${bY}, ${bX} ${bY}, ${bX - v} ${bY - h} L ${lX + v} ${lY + h} C ${lX} ${lY}, ${lX} ${lY}, ${lX + v} ${lY - h} L ${tX - v} ${tY + h} C ${tX} ${tY}, ${tX} ${tY}, ${tX + v} ${tY + h}`,
|
|
3563
|
+
roughOptions(e, true)
|
|
3564
|
+
)
|
|
3565
|
+
];
|
|
3566
|
+
}
|
|
3567
|
+
return [gen.polygon([[tX, tY], [rX, rY], [bX, bY], [lX, lY]], roughOptions(e))];
|
|
3568
|
+
}
|
|
3569
|
+
case "ellipse":
|
|
3570
|
+
return [gen.ellipse(e.width / 2, e.height / 2, e.width, e.height, roughOptions(e))];
|
|
3571
|
+
case "line":
|
|
3572
|
+
case "arrow": {
|
|
3573
|
+
const o = roughOptions(e);
|
|
3574
|
+
const points = e.points?.length ? e.points : [[0, 0]];
|
|
3575
|
+
let shapes;
|
|
3576
|
+
if (e.elbowed) shapes = [gen.path(elbowPath(points, 16), roughOptions(e, true))];
|
|
3577
|
+
else if (!e.roundness) shapes = [o.fill ? gen.polygon(points, o) : gen.linearPath(points, o)];
|
|
3578
|
+
else shapes = [gen.curve(points, o)];
|
|
3579
|
+
if (e.type === "arrow" && shapes.length) {
|
|
3580
|
+
const { startArrowhead = null, endArrowhead = "arrow" } = e;
|
|
3581
|
+
if (startArrowhead) shapes.push(...arrowheadShapes(e, shapes[0], "start", startArrowhead, o, bg));
|
|
3582
|
+
if (endArrowhead) shapes.push(...arrowheadShapes(e, shapes[0], "end", endArrowhead, o, bg));
|
|
3583
|
+
}
|
|
3584
|
+
return shapes;
|
|
3585
|
+
}
|
|
3586
|
+
case "freedraw":
|
|
3587
|
+
return isPathALoop(e.points ?? []) ? [gen.curve(e.points, { ...roughOptions(e), stroke: "none" })] : [];
|
|
3588
|
+
default:
|
|
3589
|
+
return [];
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
var esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3593
|
+
var attrs = (a2) => Object.entries(a2).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => ` ${k}="${esc(String(v))}"`).join("");
|
|
3594
|
+
function drawableToSvg(d, extra = {}) {
|
|
3595
|
+
const o = d.options;
|
|
3596
|
+
const parts = d.sets.map((set) => {
|
|
3597
|
+
const path = gen.opsToPath(set, DECIMALS);
|
|
3598
|
+
if (set.type === "path")
|
|
3599
|
+
return `<path${attrs({ d: path, stroke: o.stroke, "stroke-width": o.strokeWidth, fill: "none", "stroke-dasharray": o.strokeLineDash?.join(" ") })}/>`;
|
|
3600
|
+
if (set.type === "fillPath")
|
|
3601
|
+
return `<path${attrs({ d: path, stroke: "none", "stroke-width": 0, fill: o.fill, "fill-rule": d.shape === "curve" || d.shape === "polygon" ? "evenodd" : void 0 })}/>`;
|
|
3602
|
+
const fw = o.fillWeight < 0 ? o.strokeWidth / 2 : o.fillWeight;
|
|
3603
|
+
return `<path${attrs({ d: path, stroke: o.fill, "stroke-width": fw, fill: "none", "stroke-dasharray": o.fillLineDash?.join(" ") })}/>`;
|
|
3604
|
+
});
|
|
3605
|
+
return `<g${attrs(extra)}>${parts.join("")}</g>`;
|
|
3606
|
+
}
|
|
3607
|
+
function freedrawPath(e) {
|
|
3608
|
+
const pts = e.simulatePressure ? e.points : e.points?.length ? e.points.map(([x, y], i) => [x, y, e.pressures?.[i] ?? 0.5]) : [[0, 0, 0.5]];
|
|
3609
|
+
const outline = ae(pts, {
|
|
3610
|
+
simulatePressure: e.simulatePressure,
|
|
3611
|
+
size: (e.strokeWidth ?? 2) * 4.25,
|
|
3612
|
+
thinning: 0.6,
|
|
3613
|
+
smoothing: 0.5,
|
|
3614
|
+
streamline: 0.5,
|
|
3615
|
+
easing: (t) => Math.sin(t * Math.PI / 2),
|
|
3616
|
+
last: !!e.lastCommittedPoint
|
|
3617
|
+
});
|
|
3618
|
+
if (!outline.length) return "";
|
|
3619
|
+
const med = (a2, b2) => [(a2[0] + b2[0]) / 2, (a2[1] + b2[1]) / 2];
|
|
3620
|
+
const d = ["M", outline[0], "Q"];
|
|
3621
|
+
outline.forEach((p, i) => {
|
|
3622
|
+
if (i === outline.length - 1) d.push(p, med(p, outline[0]), "L", outline[0], "Z");
|
|
3623
|
+
else d.push(p, med(p, outline[i + 1]));
|
|
3624
|
+
});
|
|
3625
|
+
return d.map((x) => typeof x === "string" ? x : `${r2(x[0])},${r2(x[1])}`).join(" ");
|
|
3626
|
+
}
|
|
3627
|
+
var RTL = /[-߿יִ-﷽ﹰ-ﻼ]/;
|
|
3628
|
+
function textSvg(e, transform, opacity) {
|
|
3629
|
+
const lines = String(e.text ?? "").replace(/\r\n?/g, "\n").split("\n");
|
|
3630
|
+
const lineHeightPx = e.fontSize * (e.lineHeight ?? 1.25);
|
|
3631
|
+
const m = METRICS[e.fontFamily] ?? METRICS[1];
|
|
3632
|
+
const em = e.fontSize / m.unitsPerEm;
|
|
3633
|
+
const verticalOffset = em * m.ascender + (lineHeightPx - em * m.ascender + em * m.descender) / 2;
|
|
3634
|
+
const hOffset = e.textAlign === "center" ? e.width / 2 : e.textAlign === "right" ? e.width : 0;
|
|
3635
|
+
const direction = RTL.test(e.text ?? "") ? "rtl" : "ltr";
|
|
3636
|
+
const anchor = e.textAlign === "center" ? "middle" : e.textAlign === "right" || direction === "rtl" ? "end" : "start";
|
|
3637
|
+
const body = lines.map(
|
|
3638
|
+
(l2, i) => `<text${attrs({
|
|
3639
|
+
x: r2(hOffset),
|
|
3640
|
+
y: r2(i * lineHeightPx + verticalOffset),
|
|
3641
|
+
"font-family": fontFamilyCss(e.fontFamily),
|
|
3642
|
+
"font-size": `${e.fontSize}px`,
|
|
3643
|
+
fill: e.strokeColor,
|
|
3644
|
+
"text-anchor": anchor,
|
|
3645
|
+
style: "white-space: pre;",
|
|
3646
|
+
direction,
|
|
3647
|
+
"dominant-baseline": "alphabetic"
|
|
3648
|
+
})}>${esc(l2)}</text>`
|
|
3649
|
+
).join("");
|
|
3650
|
+
return `<g${attrs({ transform, "stroke-opacity": opacity !== 1 ? opacity : void 0, "fill-opacity": opacity !== 1 ? opacity : void 0 })}>${body}</g>`;
|
|
3651
|
+
}
|
|
3652
|
+
function frameLabel(f) {
|
|
3653
|
+
const fontSize = 14;
|
|
3654
|
+
const height = fontSize * 1.25;
|
|
3655
|
+
const name = f.name ?? (f.type === "magicframe" ? "AI Frame" : "Frame");
|
|
3656
|
+
const maxChars = Math.max(1, Math.floor(f.width / (fontSize * 0.55)));
|
|
3657
|
+
const text2 = name.length > maxChars ? `${name.slice(0, Math.max(1, maxChars - 1))}\u2026` : name;
|
|
3658
|
+
return {
|
|
3659
|
+
id: `${f.id}.label`,
|
|
3660
|
+
type: "text",
|
|
3661
|
+
x: f.x,
|
|
3662
|
+
y: f.y - 3 - height,
|
|
3663
|
+
width: Math.min(f.width, text2.length * fontSize * 0.55),
|
|
3664
|
+
height,
|
|
3665
|
+
angle: 0,
|
|
3666
|
+
text: text2,
|
|
3667
|
+
fontSize,
|
|
3668
|
+
fontFamily: 2,
|
|
3669
|
+
lineHeight: 1.25,
|
|
3670
|
+
textAlign: "left",
|
|
3671
|
+
strokeColor: "#999999",
|
|
3672
|
+
opacity: 100,
|
|
3673
|
+
version: 1,
|
|
3674
|
+
versionNonce: 0,
|
|
3675
|
+
isDeleted: false
|
|
3676
|
+
};
|
|
3677
|
+
}
|
|
3678
|
+
function exportSvg(input, files, opts = {}) {
|
|
3679
|
+
const bgColor = opts.backgroundColor ?? "#ffffff";
|
|
3680
|
+
const padding = opts.padding ?? 10;
|
|
3681
|
+
const live = input.filter((e) => !e.isDeleted).map((e, i) => ({ e, i })).sort((a2, b2) => a2.e.index && b2.e.index ? a2.e.index < b2.e.index ? -1 : a2.e.index > b2.e.index ? 1 : 0 : a2.i - b2.i).map((x) => x.e);
|
|
3682
|
+
const byId = new Map(live.map((e) => [e.id, e]));
|
|
3683
|
+
const frames = live.filter(isFrame);
|
|
3684
|
+
const elements = [];
|
|
3685
|
+
for (const e of live) {
|
|
3686
|
+
if (isFrame(e)) elements.push(frameLabel(e));
|
|
3687
|
+
elements.push(e);
|
|
3688
|
+
}
|
|
3689
|
+
const roots = elements.filter((e) => isFrame(e) || !e.frameId || !byId.has(e.frameId));
|
|
3690
|
+
let [minX, minY, maxX, maxY] = [Infinity, Infinity, -Infinity, -Infinity];
|
|
3691
|
+
for (const e of roots) {
|
|
3692
|
+
const [a2, b2, c, d] = elementBounds2(e);
|
|
3693
|
+
minX = Math.min(minX, a2);
|
|
3694
|
+
minY = Math.min(minY, b2);
|
|
3695
|
+
maxX = Math.max(maxX, c);
|
|
3696
|
+
maxY = Math.max(maxY, d);
|
|
3697
|
+
}
|
|
3698
|
+
if (!Number.isFinite(minX)) [minX, minY, maxX, maxY] = [0, 0, 0, 0];
|
|
3699
|
+
const width = maxX - minX + padding * 2;
|
|
3700
|
+
const height = maxY - minY + padding * 2;
|
|
3701
|
+
const ox = -minX + padding;
|
|
3702
|
+
const oy = -minY + padding;
|
|
3703
|
+
const scale = opts.scale ?? 1;
|
|
3704
|
+
const defs = [];
|
|
3705
|
+
const body = [];
|
|
3706
|
+
const clipIds = /* @__PURE__ */ new Map();
|
|
3707
|
+
frames.forEach((f, i) => {
|
|
3708
|
+
const id2 = `frame-clip-${i}`;
|
|
3709
|
+
clipIds.set(f.id, id2);
|
|
3710
|
+
const [x1, , x2] = localBounds(f);
|
|
3711
|
+
const cx = (x2 - x1) / 2;
|
|
3712
|
+
const cy = f.height / 2;
|
|
3713
|
+
defs.push(`<clipPath id="${id2}"><rect${attrs({ transform: `translate(${r2(f.x + ox)} ${r2(f.y + oy)}) rotate(${f.angle ?? 0} ${cx} ${cy})`, width: f.width, height: f.height, rx: 8, ry: 8 })}/></clipPath>`);
|
|
3714
|
+
});
|
|
3715
|
+
const clip = (e, svg2) => {
|
|
3716
|
+
const id2 = e.frameId && clipIds.get(e.frameId);
|
|
3717
|
+
return id2 ? `<g clip-path="url(#${id2})">${svg2}</g>` : svg2;
|
|
3718
|
+
};
|
|
3719
|
+
const imageSymbols = /* @__PURE__ */ new Set();
|
|
3720
|
+
const render = (e) => {
|
|
3721
|
+
const [x1, y1, x2, y2] = localBounds(e);
|
|
3722
|
+
const cx = (x2 - x1) / 2 + x1;
|
|
3723
|
+
const cy = (y2 - y1) / 2 + y1;
|
|
3724
|
+
const deg = 180 * (e.angle ?? 0) / Math.PI;
|
|
3725
|
+
let tx = e.x + ox;
|
|
3726
|
+
let ty = e.y + oy;
|
|
3727
|
+
const frame2 = e.frameId ? byId.get(e.frameId) : void 0;
|
|
3728
|
+
const opacity = (frame2?.opacity ?? 100) * (e.opacity ?? 100) / 1e4;
|
|
3729
|
+
const op = opacity !== 1 ? { "stroke-opacity": opacity, "fill-opacity": opacity } : {};
|
|
3730
|
+
const transform = () => `translate(${r2(tx)} ${r2(ty)}) rotate(${r2(deg)} ${r2(cx)} ${r2(cy)})`;
|
|
3731
|
+
switch (e.type) {
|
|
3732
|
+
case "rectangle":
|
|
3733
|
+
case "diamond":
|
|
3734
|
+
case "ellipse": {
|
|
3735
|
+
const [shape2] = shapeOf(e, bgColor);
|
|
3736
|
+
body.push(clip(e, drawableToSvg(shape2, { ...op, "stroke-linecap": "round", transform: transform() })));
|
|
3737
|
+
return;
|
|
3738
|
+
}
|
|
3739
|
+
case "line":
|
|
3740
|
+
case "arrow": {
|
|
3741
|
+
const label2 = e.boundElements?.find((b2) => b2.type === "text");
|
|
3742
|
+
const t = label2 && byId.get(label2.id);
|
|
3743
|
+
let mask = "";
|
|
3744
|
+
if (t) {
|
|
3745
|
+
mask = `<mask id="mask-${esc(e.id)}"><rect x="0" y="0" fill="#fff" width="${r2(e.width + 100 + tx)}" height="${r2(e.height + 100 + ty)}"/><rect fill="#000" x="${r2(tx + t.x - e.x)}" y="${r2(ty + t.y - e.y)}" width="${r2(t.width)}" height="${r2(t.height)}" opacity="1"/></mask>`;
|
|
3746
|
+
}
|
|
3747
|
+
const loopFill = e.type === "line" && isPathALoop(e.points ?? []) && e.backgroundColor !== "transparent" ? { "fill-rule": "evenodd" } : {};
|
|
3748
|
+
const g = shapeOf(e, bgColor).map((s) => drawableToSvg(s, { ...op, ...loopFill, transform: transform() })).join("");
|
|
3749
|
+
body.push(clip(e, `<g${attrs({ mask: t ? `url(#mask-${e.id})` : void 0, "stroke-linecap": "round" })}>${g}</g>${mask}`));
|
|
3750
|
+
return;
|
|
3751
|
+
}
|
|
3752
|
+
case "freedraw": {
|
|
3753
|
+
const [fill2] = shapeOf(e, bgColor);
|
|
3754
|
+
const path = `<path fill="${esc(e.strokeColor)}" d="${freedrawPath(e)}"/>`;
|
|
3755
|
+
const inner = fill2 ? drawableToSvg(fill2).replace(/<\/g>$/, `${path}</g>`) : `<g>${path}</g>`;
|
|
3756
|
+
body.push(clip(e, inner.replace(/^<g/, `<g${attrs({ ...op, transform: transform(), stroke: "none" })}`)));
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
case "image": {
|
|
3760
|
+
const file = e.fileId && files[e.fileId];
|
|
3761
|
+
if (!file) return;
|
|
3762
|
+
const symbolId = `image-${file.id}`;
|
|
3763
|
+
if (!imageSymbols.has(symbolId)) {
|
|
3764
|
+
imageSymbols.add(symbolId);
|
|
3765
|
+
defs.unshift(`<symbol id="${esc(symbolId)}" viewBox="0 0 1 1" preserveAspectRatio="none"><image href="${esc(file.dataURL)}" preserveAspectRatio="none" width="1" height="1"/></symbol>`);
|
|
3766
|
+
}
|
|
3767
|
+
const flip = e.scale && (e.scale[0] !== 1 || e.scale[1] !== 1) ? `translate(${cx} ${cy}) scale(${e.scale[0]} ${e.scale[1]}) translate(${-cx} ${-cy})` : void 0;
|
|
3768
|
+
let g = `<g${attrs({ transform: transform() })}><use${attrs({ href: `#${symbolId}`, width: Math.round(e.width), height: Math.round(e.height), opacity, transform: flip })}/></g>`;
|
|
3769
|
+
if (e.roundness) {
|
|
3770
|
+
const r = cornerRadius(Math.min(e.width, e.height), e);
|
|
3771
|
+
const id2 = `image-clip-${esc(e.id)}`;
|
|
3772
|
+
defs.push(`<clipPath id="${id2}"><rect width="${e.width}" height="${e.height}" rx="${r}" ry="${r}"/></clipPath>`);
|
|
3773
|
+
g = g.replace(/^<g/, `<g clip-path="url(#${id2})"`);
|
|
3774
|
+
}
|
|
3775
|
+
body.push(clip(e, g));
|
|
3776
|
+
return;
|
|
3777
|
+
}
|
|
3778
|
+
case "frame":
|
|
3779
|
+
case "magicframe":
|
|
3780
|
+
body.push(`<rect${attrs({ transform: transform(), width: `${e.width}px`, height: `${e.height}px`, rx: 8, ry: 8, fill: "none", stroke: "#bbb", "stroke-width": 2 })}/>`);
|
|
3781
|
+
return;
|
|
3782
|
+
case "text": {
|
|
3783
|
+
body.push(clip(e, textSvg(e, transform(), opacity)));
|
|
3784
|
+
return;
|
|
3785
|
+
}
|
|
3786
|
+
default: {
|
|
3787
|
+
if (e.type === "embeddable" || e.type === "iframe") {
|
|
3788
|
+
body.push(`<rect${attrs({ transform: transform(), width: e.width, height: e.height, fill: "#d3d3d3", stroke: "#bbb" })}/>`);
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
};
|
|
3793
|
+
for (const e of elements) {
|
|
3794
|
+
if (e.type === "text" && e.containerId && byId.has(e.containerId)) continue;
|
|
3795
|
+
render(e);
|
|
3796
|
+
const label2 = e.boundElements?.find((b2) => b2.type === "text");
|
|
3797
|
+
const t = label2 && byId.get(label2.id);
|
|
3798
|
+
if (t) render(t);
|
|
3799
|
+
}
|
|
3800
|
+
const style = opts.fontCss ? `<style class="style-fonts">${opts.fontCss}</style>` : "";
|
|
3801
|
+
const bg = opts.background !== false ? `<rect x="0" y="0" width="${r2(width)}" height="${r2(height)}" fill="${esc(bgColor)}"/>` : "";
|
|
3802
|
+
const svg = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${r2(width)} ${r2(height)}" width="${r2(width * scale)}" height="${r2(height * scale)}"><!-- svg-source:vizo --><defs>${defs.join("")}${style}</defs>${bg}${body.join("")}</svg>`;
|
|
3803
|
+
return { svg, width, height };
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
// src/ttf.ts
|
|
3807
|
+
import { readFileSync } from "node:fs";
|
|
3808
|
+
function loadFace(path) {
|
|
3809
|
+
const b2 = readFileSync(path);
|
|
3810
|
+
const tables = /* @__PURE__ */ new Map();
|
|
3811
|
+
for (let i = 0; i < b2.readUInt16BE(4); i++) tables.set(b2.toString("latin1", 12 + i * 16, 16 + i * 16), b2.readUInt32BE(12 + i * 16 + 8));
|
|
3812
|
+
const head = tables.get("head");
|
|
3813
|
+
const unitsPerEm = b2.readUInt16BE(head + 18);
|
|
3814
|
+
const numberOfHMetrics = b2.readUInt16BE(tables.get("hhea") + 34);
|
|
3815
|
+
const hmtx = tables.get("hmtx");
|
|
3816
|
+
const glyphOf = cmap(b2, tables.get("cmap"));
|
|
3817
|
+
const width = (g) => b2.readUInt16BE(hmtx + Math.min(g, numberOfHMetrics - 1) * 4);
|
|
3818
|
+
return {
|
|
3819
|
+
has: (cp) => glyphOf(cp) > 0,
|
|
3820
|
+
advance: (cp) => width(glyphOf(cp)) / unitsPerEm
|
|
3821
|
+
};
|
|
3822
|
+
}
|
|
3823
|
+
function cmap(b2, at) {
|
|
3824
|
+
let f4 = -1;
|
|
3825
|
+
let f12 = -1;
|
|
3826
|
+
for (let i = 0; i < b2.readUInt16BE(at + 2); i++) {
|
|
3827
|
+
const r = at + 4 + i * 8;
|
|
3828
|
+
const [platform, encoding] = [b2.readUInt16BE(r), b2.readUInt16BE(r + 2)];
|
|
3829
|
+
if (!(platform === 0 || platform === 3 && (encoding === 1 || encoding === 10))) continue;
|
|
3830
|
+
const sub = at + b2.readUInt32BE(r + 4);
|
|
3831
|
+
const format = b2.readUInt16BE(sub);
|
|
3832
|
+
if (format === 12) f12 = sub;
|
|
3833
|
+
else if (format === 4) f4 = sub;
|
|
3834
|
+
}
|
|
3835
|
+
if (f12 >= 0) {
|
|
3836
|
+
const n = b2.readUInt32BE(f12 + 12);
|
|
3837
|
+
return (cp) => {
|
|
3838
|
+
let [lo, hi] = [0, n - 1];
|
|
3839
|
+
while (lo <= hi) {
|
|
3840
|
+
const mid = lo + hi >> 1;
|
|
3841
|
+
const g = f12 + 16 + mid * 12;
|
|
3842
|
+
const [start, end] = [b2.readUInt32BE(g), b2.readUInt32BE(g + 4)];
|
|
3843
|
+
if (cp < start) hi = mid - 1;
|
|
3844
|
+
else if (cp > end) lo = mid + 1;
|
|
3845
|
+
else return b2.readUInt32BE(g + 8) + cp - start;
|
|
3846
|
+
}
|
|
3847
|
+
return 0;
|
|
3848
|
+
};
|
|
3849
|
+
}
|
|
3850
|
+
if (f4 < 0) return () => 0;
|
|
3851
|
+
const segX2 = b2.readUInt16BE(f4 + 6);
|
|
3852
|
+
const ends = f4 + 14;
|
|
3853
|
+
const starts = ends + segX2 + 2;
|
|
3854
|
+
const deltas = starts + segX2;
|
|
3855
|
+
const offsets = deltas + segX2;
|
|
3856
|
+
return (cp) => {
|
|
3857
|
+
if (cp > 65535) return 0;
|
|
3858
|
+
for (let i = 0; i < segX2; i += 2) {
|
|
3859
|
+
if (cp > b2.readUInt16BE(ends + i)) continue;
|
|
3860
|
+
const start = b2.readUInt16BE(starts + i);
|
|
3861
|
+
if (cp < start) return 0;
|
|
3862
|
+
const delta = b2.readInt16BE(deltas + i);
|
|
3863
|
+
const ro = b2.readUInt16BE(offsets + i);
|
|
3864
|
+
if (!ro) return cp + delta & 65535;
|
|
3865
|
+
const g = b2.readUInt16BE(offsets + i + ro + (cp - start) * 2);
|
|
3866
|
+
return g ? g + delta & 65535 : 0;
|
|
3867
|
+
}
|
|
3868
|
+
return 0;
|
|
3869
|
+
};
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
// src/export.ts
|
|
3873
|
+
var EXPORT_TOOL = {
|
|
3874
|
+
name: "export_image",
|
|
3875
|
+
title: "Export image",
|
|
3876
|
+
description: "Save a board, or some of its diagrams/elements, as a PNG or SVG file on this machine, drawn the way the board shows it (hand-drawn strokes, fonts, images). Returns the file path, and by default a small preview image so you can check the result.",
|
|
3877
|
+
inputSchema: {
|
|
3878
|
+
type: "object",
|
|
3879
|
+
properties: {
|
|
3880
|
+
boardId: { type: "string", description: "Board id (e.g. 'b_xxx') or full board URL" },
|
|
3881
|
+
format: { type: "string", enum: ["png", "svg"], description: "Default png" },
|
|
3882
|
+
diagramIds: { type: "array", items: { type: "string" }, description: "Only these diagrams (ids from get_board), with their frame and title" },
|
|
3883
|
+
elementIds: { type: "array", items: { type: "string" }, description: "Only these elements; a frame brings its contents, a shape its label" },
|
|
3884
|
+
scale: { type: "number", minimum: 0.1, maximum: 8, description: "PNG pixels per board unit (default 2 = sharp on high-DPI screens)" },
|
|
3885
|
+
background: { type: "boolean", description: "Fill with the board background (default true); false gives a transparent PNG/SVG" },
|
|
3886
|
+
path: { type: "string", description: "Output file. Default: <board title>.<format> in the current directory (never overwrites; adds -2, -3\u2026)" },
|
|
3887
|
+
preview: { type: "boolean", description: "Also return a small PNG preview to look at (default true)" }
|
|
3888
|
+
},
|
|
3889
|
+
required: ["boardId"],
|
|
3890
|
+
additionalProperties: false
|
|
3891
|
+
},
|
|
3892
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
3893
|
+
};
|
|
3894
|
+
var MAX_SIDE = 16384;
|
|
3895
|
+
var MAX_AREA = 16384 * 16384;
|
|
3896
|
+
var PREVIEW_MAX_SIDE = 1200;
|
|
3897
|
+
var ExportError = class extends Error {
|
|
3898
|
+
};
|
|
3899
|
+
function pickElements(scene, args) {
|
|
3900
|
+
const all = scene.elements.filter((e) => !e.isDeleted);
|
|
3901
|
+
if (!args.diagramIds?.length && !args.elementIds?.length) return all;
|
|
3902
|
+
const byId = new Map(all.map((e) => [e.id, e]));
|
|
3903
|
+
const want = /* @__PURE__ */ new Set();
|
|
3904
|
+
const unknown = [];
|
|
3905
|
+
for (const d of args.diagramIds ?? []) {
|
|
3906
|
+
const diagram = scene.diagrams.find((x) => x.id === d);
|
|
3907
|
+
if (diagram && byId.has(diagram.frameElementId)) want.add(diagram.frameElementId);
|
|
3908
|
+
else unknown.push(`diagram '${d}'`);
|
|
3909
|
+
}
|
|
3910
|
+
for (const id2 of args.elementIds ?? []) {
|
|
3911
|
+
if (byId.has(id2)) want.add(id2);
|
|
3912
|
+
else unknown.push(`element '${id2}'`);
|
|
3913
|
+
}
|
|
3914
|
+
if (unknown.length) throw new ExportError(`Not on this board: ${unknown.join(", ")}. Use get_board for ids.`);
|
|
3915
|
+
for (const e of all) if (e.frameId && want.has(e.frameId)) want.add(e.id);
|
|
3916
|
+
for (const e of all) if (e.type === "text" && e.containerId && want.has(e.containerId)) want.add(e.id);
|
|
3917
|
+
return all.filter((e) => want.has(e.id));
|
|
3918
|
+
}
|
|
3919
|
+
var FONT_DIR = new URL("./fonts/", import.meta.url);
|
|
3920
|
+
var manifest = null;
|
|
3921
|
+
var fonts = () => manifest ??= JSON.parse(readFileSync2(new URL("fonts.json", FONT_DIR), "utf8"));
|
|
3922
|
+
function inRange(range, cp) {
|
|
3923
|
+
if (!range) return true;
|
|
3924
|
+
return range.split(",").some((r) => {
|
|
3925
|
+
const [a2, b2] = r.trim().replace(/^U\+/i, "").split("-");
|
|
3926
|
+
const lo = parseInt(a2, 16);
|
|
3927
|
+
return cp >= lo && cp <= (b2 ? parseInt(b2, 16) : lo);
|
|
3928
|
+
});
|
|
3929
|
+
}
|
|
3930
|
+
function fontCss(elements) {
|
|
3931
|
+
const used = fontsUsed(elements);
|
|
3932
|
+
const chars = /* @__PURE__ */ new Map();
|
|
3933
|
+
for (const e of elements) {
|
|
3934
|
+
const id2 = e.type === "text" ? e.fontFamily ?? 5 : e.type === "frame" || e.type === "magicframe" ? 2 : null;
|
|
3935
|
+
if (id2 === null) continue;
|
|
3936
|
+
const set = chars.get(id2) ?? /* @__PURE__ */ new Set();
|
|
3937
|
+
for (const ch of String(e.type === "text" ? e.text : e.name ?? "Frame")) set.add(ch.codePointAt(0));
|
|
3938
|
+
chars.set(id2, set);
|
|
3939
|
+
}
|
|
3940
|
+
return fonts().filter((f) => used.has(f.id) && [...chars.get(f.id) ?? []].some((cp) => inRange(f.unicodeRange, cp))).map((f) => {
|
|
3941
|
+
const data = readFileSync2(new URL(f.file, FONT_DIR)).toString("base64");
|
|
3942
|
+
return `@font-face { font-family: "${f.family}"; src: url(data:font/woff2;base64,${data}); ${f.unicodeRange ? `unicode-range: ${f.unicodeRange}; ` : ""}}`;
|
|
3943
|
+
}).join("\n");
|
|
3944
|
+
}
|
|
3945
|
+
var unescapeXml = (s) => s.replace(/&(lt|gt|quot|amp);/g, (_, e) => ({ lt: "<", gt: ">", quot: '"', amp: "&" })[e]);
|
|
3946
|
+
var escapeXml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3947
|
+
var BORROW_ORDER = ["Virgil", "Excalifont", "Comic Shanns", "Liberation Sans", "Nunito", "Cascadia", "Lilita One"];
|
|
3948
|
+
var ttfPath = (f) => fileURLToPath(new URL(f.file.replace(/\.woff2$/, ".ttf"), FONT_DIR));
|
|
3949
|
+
var faces = /* @__PURE__ */ new Map();
|
|
3950
|
+
var face = (f) => faces.get(f.file) ?? faces.set(f.file, loadFace(ttfPath(f))).get(f.file);
|
|
3951
|
+
function fontFor(family, cp) {
|
|
3952
|
+
const own = fonts().filter((f) => f.family === family);
|
|
3953
|
+
const others = fonts().filter((f) => f.family !== family).sort((a2, b2) => BORROW_ORDER.indexOf(a2.family) - BORROW_ORDER.indexOf(b2.family));
|
|
3954
|
+
return [...own, ...others].find((f) => face(f).has(cp)) ?? null;
|
|
3955
|
+
}
|
|
3956
|
+
function guessAdvance(cp) {
|
|
3957
|
+
if (cp === 8205 || cp >= 65024 && cp <= 65039) return 0;
|
|
3958
|
+
const wide = cp >= 4352 && cp <= 4447 || cp >= 11904 && cp <= 42191 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65280 && cp <= 65376 || cp >= 126976;
|
|
3959
|
+
return wide ? 1 : 0.8;
|
|
3960
|
+
}
|
|
3961
|
+
var setAttr = (attrs2, name, value) => new RegExp(` ${name}="[^"]*"`).test(attrs2) ? attrs2.replace(new RegExp(` ${name}="[^"]*"`), ` ${name}="${value}"`) : `${attrs2} ${name}="${value}"`;
|
|
3962
|
+
var EMOJI = new RegExp("\\p{Extended_Pictographic}", "u");
|
|
3963
|
+
function useTtfFamilies(svg) {
|
|
3964
|
+
return svg.replace(/<text([^>]*?) font-family="([^"]*)"([^>]*)>([^<]*)<\/text>/g, (_, pre, list, post, body) => {
|
|
3965
|
+
let family = list.split(",")[0].trim();
|
|
3966
|
+
if (family === "Helvetica") family = "Liberation Sans";
|
|
3967
|
+
const attrs2 = `${pre}${post} xml:space="preserve"`;
|
|
3968
|
+
const runs = [];
|
|
3969
|
+
for (const ch of unescapeXml(body)) {
|
|
3970
|
+
const cp = ch.codePointAt(0);
|
|
3971
|
+
const last = runs.at(-1);
|
|
3972
|
+
const joiner = cp === 8205 || cp >= 65024 && cp <= 65039 || cp >= 127995 && cp <= 127999;
|
|
3973
|
+
if (last && (ch === " " || joiner)) {
|
|
3974
|
+
last.text += ch;
|
|
3975
|
+
continue;
|
|
3976
|
+
}
|
|
3977
|
+
const emoji = EMOJI.test(ch) && cp > 255;
|
|
3978
|
+
const font = emoji ? null : fontFor(family, cp);
|
|
3979
|
+
if (last && last.font === font && last.emoji === emoji) last.text += ch;
|
|
3980
|
+
else runs.push({ font, emoji, text: ch });
|
|
3981
|
+
}
|
|
3982
|
+
const fontName = (r) => r.font?.ttfFamily ?? family;
|
|
3983
|
+
if (runs.length === 1 && !runs[0].emoji) return `<text${attrs2} font-family="${fontName(runs[0])}">${body}</text>`;
|
|
3984
|
+
if (!runs.length || / direction="rtl"/.test(attrs2)) return `<text${attrs2} font-family="${family}">${body}</text>`;
|
|
3985
|
+
const size = parseFloat(/font-size="([\d.]+)/.exec(attrs2)?.[1] ?? "20");
|
|
3986
|
+
const x = parseFloat(/ x="([-\d.]+)"/.exec(attrs2)?.[1] ?? "0");
|
|
3987
|
+
const anchor = / text-anchor="(\w+)"/.exec(attrs2)?.[1] ?? "start";
|
|
3988
|
+
const widths = runs.map((r) => [...r.text].reduce((w, ch) => w + (r.font ? face(r.font).advance(ch.codePointAt(0)) : guessAdvance(ch.codePointAt(0))), 0) * size);
|
|
3989
|
+
const total = widths.reduce((a2, b2) => a2 + b2, 0);
|
|
3990
|
+
let at = anchor === "middle" ? x - total / 2 : anchor === "end" ? x - total : x;
|
|
3991
|
+
return runs.map((r, i) => {
|
|
3992
|
+
const a2 = setAttr(setAttr(attrs2, "x", Math.round(at * 100) / 100), "text-anchor", "start");
|
|
3993
|
+
at += widths[i];
|
|
3994
|
+
return r.emoji ? "" : `<text${a2} font-family="${fontName(r)}">${escapeXml(r.text)}</text>`;
|
|
3995
|
+
}).join("");
|
|
3996
|
+
});
|
|
3997
|
+
}
|
|
3998
|
+
async function rasterize(svg, width) {
|
|
3999
|
+
const { Resvg } = await import("@resvg/resvg-js");
|
|
4000
|
+
svg = useTtfFamilies(svg);
|
|
4001
|
+
const fontFiles = fonts().map(ttfPath);
|
|
4002
|
+
const png = new Resvg(svg, {
|
|
4003
|
+
fitTo: { mode: "width", value: Math.max(1, Math.round(width)) },
|
|
4004
|
+
// System fonts cover what Excalidraw's own fonts don't (CJK, emoji); Helvetica falls back to Liberation Sans.
|
|
4005
|
+
font: { fontFiles, loadSystemFonts: true, defaultFontFamily: "Liberation Sans", sansSerifFamily: "Liberation Sans" },
|
|
4006
|
+
imageRendering: 0,
|
|
4007
|
+
shapeRendering: 2,
|
|
4008
|
+
textRendering: 1
|
|
4009
|
+
}).render();
|
|
4010
|
+
return { data: png.asPng(), width: png.width, height: png.height };
|
|
4011
|
+
}
|
|
4012
|
+
function outputPath(requested, title, format) {
|
|
4013
|
+
if (requested) {
|
|
4014
|
+
const p2 = resolve(requested);
|
|
4015
|
+
return extname(p2).toLowerCase() === `.${format}` ? p2 : `${p2}.${format}`;
|
|
4016
|
+
}
|
|
4017
|
+
const base2 = (title || "board").replace(/[\\/:*?"<>|\x00-\x1f]+/g, "-").trim() || "board";
|
|
4018
|
+
let p = resolve(`${base2}.${format}`);
|
|
4019
|
+
for (let n = 2; existsSync(p); n++) p = resolve(`${base2}-${n}.${format}`);
|
|
4020
|
+
return p;
|
|
4021
|
+
}
|
|
4022
|
+
async function exportImage(scene, args) {
|
|
4023
|
+
const format = args.format ?? "png";
|
|
4024
|
+
const elements = pickElements(scene, args);
|
|
4025
|
+
if (!elements.length) throw new ExportError("Nothing to export: the board is empty.");
|
|
4026
|
+
const files = Object.fromEntries(scene.files.map((f) => [f.id, f]));
|
|
4027
|
+
const background = args.background !== false;
|
|
4028
|
+
const scale = args.scale ?? 2;
|
|
4029
|
+
const svgOpts = { background, backgroundColor: "#ffffff", fontCss: fontCss(elements) };
|
|
4030
|
+
const { svg, width, height } = exportSvg(elements, files, { ...svgOpts, scale: format === "svg" ? 1 : scale });
|
|
4031
|
+
const path = outputPath(args.path, scene.title ?? "", format);
|
|
4032
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
4033
|
+
let size;
|
|
4034
|
+
let bytes;
|
|
4035
|
+
if (format === "svg") {
|
|
4036
|
+
writeFileSync(path, svg);
|
|
4037
|
+
size = { width: Math.round(width), height: Math.round(height) };
|
|
4038
|
+
bytes = Buffer.byteLength(svg);
|
|
4039
|
+
} else {
|
|
4040
|
+
const w = Math.round(width * scale);
|
|
4041
|
+
const h = Math.round(height * scale);
|
|
4042
|
+
if (w > MAX_SIDE || h > MAX_SIDE || w * h > MAX_AREA)
|
|
4043
|
+
throw new ExportError(`${w}\xD7${h} px is too large (max ${MAX_SIDE} px per side). Use a smaller scale, or export fewer diagrams.`);
|
|
4044
|
+
const png = await rasterize(svg, w);
|
|
4045
|
+
writeFileSync(path, png.data);
|
|
4046
|
+
size = { width: png.width, height: png.height };
|
|
4047
|
+
bytes = png.data.length;
|
|
4048
|
+
}
|
|
4049
|
+
const content = [
|
|
4050
|
+
{ type: "text", text: JSON.stringify({ path, format, ...size, bytes, elements: elements.length }, null, 1) }
|
|
4051
|
+
];
|
|
4052
|
+
if (args.preview !== false) {
|
|
4053
|
+
const fit = Math.min(1, PREVIEW_MAX_SIDE / Math.max(width, height));
|
|
4054
|
+
const preview = await rasterize(svg, width * fit);
|
|
4055
|
+
content.push({ type: "image", data: Buffer.from(preview.data).toString("base64"), mimeType: "image/png" });
|
|
4056
|
+
}
|
|
4057
|
+
return { content };
|
|
4058
|
+
}
|
|
4059
|
+
|
|
4060
|
+
// src/index.ts
|
|
4061
|
+
var PROXY_HEADER = "X-Vizo-Proxy";
|
|
4062
|
+
var PUT_TOOL = "put_diagram_elements";
|
|
4063
|
+
var SCENE_TOOL = "get_board_scene";
|
|
4064
|
+
var SIZES_TOOL = "get_image_sizes";
|
|
4065
|
+
var HIDDEN = /* @__PURE__ */ new Set([PUT_TOOL, SCENE_TOOL, SIZES_TOOL]);
|
|
4066
|
+
var IMAGE_TOOL = "add_image";
|
|
4067
|
+
var MAX_IMAGE_BYTES = 47e5;
|
|
4068
|
+
var LOCAL_TOOLS = /* @__PURE__ */ new Set(["draw_diagram", "update_diagram"]);
|
|
4069
|
+
var toolError = (text2) => ({ content: [{ type: "text", text: text2 }], isError: true });
|
|
4070
|
+
function arg(name) {
|
|
4071
|
+
const i = process.argv.indexOf(`--${name}`);
|
|
4072
|
+
if (i >= 0) return process.argv[i + 1];
|
|
4073
|
+
const eq = process.argv.find((a2) => a2.startsWith(`--${name}=`));
|
|
4074
|
+
return eq?.slice(name.length + 3);
|
|
4075
|
+
}
|
|
4076
|
+
var log = (...a2) => console.error("[vizo-mcp]", ...a2);
|
|
4077
|
+
async function main() {
|
|
4078
|
+
const base2 = (arg("url") ?? process.env.VIZO_URL ?? "").replace(/\/$/, "");
|
|
4079
|
+
const token = arg("token") ?? process.env.VIZO_TOKEN;
|
|
4080
|
+
if (!base2 || !token) {
|
|
4081
|
+
log("usage: vizo-mcp --url <https://your-vizo-host> --token <vz_\u2026> (or VIZO_URL / VIZO_TOKEN)");
|
|
4082
|
+
log("Create a token at <url>/settings.");
|
|
4083
|
+
process.exit(2);
|
|
4084
|
+
}
|
|
4085
|
+
const endpoint = new URL(base2.endsWith("/mcp") ? base2 : `${base2}/mcp`);
|
|
4086
|
+
const upstream = new Client({ name: "vizo-mcp-proxy", version: "0.3.0" });
|
|
4087
|
+
await upstream.connect(
|
|
4088
|
+
new StreamableHTTPClientTransport(endpoint, { requestInit: { headers: { Authorization: `Bearer ${token}`, [PROXY_HEADER]: String(LAYOUT_VERSION) } } })
|
|
4089
|
+
);
|
|
4090
|
+
const caps = upstream.getServerCapabilities() ?? {};
|
|
4091
|
+
log(`connected to ${endpoint.href} (local layout v${LAYOUT_VERSION})`);
|
|
4092
|
+
const server = new Server(
|
|
4093
|
+
{ name: "vizo", version: "0.3.0" },
|
|
4094
|
+
{
|
|
4095
|
+
capabilities: {
|
|
4096
|
+
tools: caps.tools ? {} : void 0,
|
|
4097
|
+
resources: caps.resources ? {} : void 0,
|
|
4098
|
+
prompts: caps.prompts ? {} : void 0
|
|
4099
|
+
},
|
|
4100
|
+
instructions: `${upstream.getInstructions() ?? ""}
|
|
4101
|
+
|
|
4102
|
+
This local proxy also has export_image: save a board or some of its diagrams as PNG/SVG on this machine (e.g. for docs or a PR), and look at the returned preview to check your drawing.`.trim()
|
|
4103
|
+
}
|
|
4104
|
+
);
|
|
4105
|
+
if (caps.tools) {
|
|
4106
|
+
let serverSupportsLocal;
|
|
4107
|
+
let serverSupportsExport = false;
|
|
4108
|
+
server.setRequestHandler(ListToolsRequestSchema, async (req) => {
|
|
4109
|
+
const r = await upstream.listTools(req.params);
|
|
4110
|
+
if (r.tools.some((t) => t.name === PUT_TOOL)) serverSupportsLocal = true;
|
|
4111
|
+
serverSupportsExport = r.tools.some((t) => t.name === SCENE_TOOL);
|
|
4112
|
+
const tools = r.tools.filter((t) => !HIDDEN.has(t.name)).map((t) => t.name === IMAGE_TOOL ? withPath(t) : t);
|
|
4113
|
+
return { ...r, tools: serverSupportsExport && !r.nextCursor ? [...tools, EXPORT_TOOL] : tools };
|
|
4114
|
+
});
|
|
4115
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
4116
|
+
const { name, arguments: args = {} } = req.params;
|
|
4117
|
+
if (name === EXPORT_TOOL.name) return exportLocally(args);
|
|
4118
|
+
if (name === IMAGE_TOOL && typeof args.path === "string") {
|
|
4119
|
+
const { path, ...rest } = args;
|
|
4120
|
+
let dataURL;
|
|
4121
|
+
try {
|
|
4122
|
+
dataURL = readImage(path);
|
|
4123
|
+
} catch (e) {
|
|
4124
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4125
|
+
}
|
|
4126
|
+
return upstream.callTool({ ...req.params, arguments: { ...rest, dataURL } });
|
|
4127
|
+
}
|
|
4128
|
+
if (!LOCAL_TOOLS.has(name)) return upstream.callTool(req.params);
|
|
4129
|
+
serverSupportsLocal ??= (await upstream.listTools()).tools.some((t) => t.name === PUT_TOOL);
|
|
4130
|
+
if (!serverSupportsLocal) {
|
|
4131
|
+
return toolError(
|
|
4132
|
+
`This Vizo server (${base2}) does not accept locally rendered diagrams (no ${PUT_TOOL}); it is older than this vizo-mcp (layout v${LAYOUT_VERSION}). Nothing was drawn. Update the server, or use a vizo-mcp version matching it.`
|
|
4133
|
+
);
|
|
4134
|
+
}
|
|
4135
|
+
return renderLocally(name, args);
|
|
4136
|
+
});
|
|
4137
|
+
}
|
|
4138
|
+
if (caps.resources) {
|
|
4139
|
+
server.setRequestHandler(ListResourcesRequestSchema, (req) => upstream.listResources(req.params));
|
|
4140
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, (req) => upstream.listResourceTemplates(req.params));
|
|
4141
|
+
server.setRequestHandler(ReadResourceRequestSchema, (req) => upstream.readResource(req.params));
|
|
4142
|
+
}
|
|
4143
|
+
if (caps.prompts) {
|
|
4144
|
+
server.setRequestHandler(ListPromptsRequestSchema, (req) => upstream.listPrompts(req.params));
|
|
4145
|
+
server.setRequestHandler(GetPromptRequestSchema, (req) => upstream.getPrompt(req.params));
|
|
4146
|
+
}
|
|
4147
|
+
async function renderLocally(name, args) {
|
|
4148
|
+
let spec;
|
|
4149
|
+
try {
|
|
4150
|
+
spec = parseSpec(args.spec);
|
|
4151
|
+
} catch (e) {
|
|
4152
|
+
if (e instanceof SpecError) return toolError(e.message);
|
|
4153
|
+
throw e;
|
|
4154
|
+
}
|
|
4155
|
+
const drawing = name === "draw_diagram";
|
|
4156
|
+
const diagramId = drawing ? `d-${randomId(8)}` : String(args.diagramId ?? "");
|
|
4157
|
+
if (!diagramId) return toolError("diagramId is required");
|
|
4158
|
+
const imageIds = specImageIds(spec);
|
|
4159
|
+
let images = {};
|
|
4160
|
+
if (imageIds.length) {
|
|
4161
|
+
const res = await upstream.callTool({ name: SIZES_TOOL, arguments: { boardId: args.boardId, fileIds: imageIds } });
|
|
4162
|
+
const body = res.content?.find((c) => c.type === "text")?.text ?? "";
|
|
4163
|
+
if (res.isError) return toolError(body || "Couldn't read the board's images.");
|
|
4164
|
+
images = Object.fromEntries(JSON.parse(body).map((f) => [f.id, { width: f.width, height: f.height }]));
|
|
4165
|
+
}
|
|
4166
|
+
let r;
|
|
4167
|
+
try {
|
|
4168
|
+
r = await renderDiagram(spec, { diagramId, images });
|
|
4169
|
+
} catch (e) {
|
|
4170
|
+
if (e instanceof SpecError) return toolError(e.message);
|
|
4171
|
+
throw e;
|
|
4172
|
+
}
|
|
4173
|
+
return upstream.callTool({
|
|
4174
|
+
name: PUT_TOOL,
|
|
4175
|
+
arguments: {
|
|
4176
|
+
boardId: args.boardId,
|
|
4177
|
+
mode: drawing ? "draw" : "update",
|
|
4178
|
+
layoutVersion: LAYOUT_VERSION,
|
|
4179
|
+
diagramId,
|
|
4180
|
+
spec,
|
|
4181
|
+
placement: args.placement,
|
|
4182
|
+
elements: r.elements
|
|
4183
|
+
}
|
|
4184
|
+
});
|
|
4185
|
+
}
|
|
4186
|
+
async function exportLocally(args) {
|
|
4187
|
+
if (!args?.boardId) return toolError("boardId is required");
|
|
4188
|
+
const r = await upstream.callTool({ name: SCENE_TOOL, arguments: { boardId: args.boardId } });
|
|
4189
|
+
const body = r.content?.find((c) => c.type === "text")?.text ?? "";
|
|
4190
|
+
if (r.isError) return toolError(body || "Couldn't read the board.");
|
|
4191
|
+
try {
|
|
4192
|
+
return await exportImage(JSON.parse(body), args);
|
|
4193
|
+
} catch (e) {
|
|
4194
|
+
if (e instanceof ExportError) return toolError(e.message);
|
|
4195
|
+
log("export failed:", e);
|
|
4196
|
+
return toolError(`Export failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
await server.connect(new StdioServerTransport());
|
|
4200
|
+
const shutdown = async () => {
|
|
4201
|
+
await Promise.allSettled([server.close(), upstream.close()]);
|
|
4202
|
+
process.exit(0);
|
|
4203
|
+
};
|
|
4204
|
+
process.on("SIGINT", shutdown);
|
|
4205
|
+
process.on("SIGTERM", shutdown);
|
|
4206
|
+
process.stdin.on("close", shutdown);
|
|
4207
|
+
}
|
|
4208
|
+
function withPath(t) {
|
|
4209
|
+
const schema = t.inputSchema ?? { type: "object", properties: {} };
|
|
4210
|
+
return {
|
|
4211
|
+
...t,
|
|
4212
|
+
description: `${t.description ?? ""} Here you can also pass a local file \`path\` instead of url/dataURL.`,
|
|
4213
|
+
inputSchema: {
|
|
4214
|
+
...schema,
|
|
4215
|
+
properties: { path: { type: "string", description: "Image file on this machine (absolute, or relative to the working directory); use instead of url/dataURL" }, ...schema.properties }
|
|
4216
|
+
}
|
|
4217
|
+
};
|
|
4218
|
+
}
|
|
4219
|
+
var MIME = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", bmp: "image/bmp", svg: "image/svg+xml" };
|
|
4220
|
+
function readImage(path) {
|
|
4221
|
+
const full = resolve2(path);
|
|
4222
|
+
let size;
|
|
4223
|
+
try {
|
|
4224
|
+
size = statSync(full).size;
|
|
4225
|
+
} catch {
|
|
4226
|
+
throw new Error(`No such file: ${full}`);
|
|
4227
|
+
}
|
|
4228
|
+
if (size > MAX_IMAGE_BYTES) throw new Error(`Image too large (${(size / 1e6).toFixed(1)} MB; max ${MAX_IMAGE_BYTES / 1e6} MB). Use a smaller or more compressed image.`);
|
|
4229
|
+
const mime = MIME[full.split(".").pop()?.toLowerCase() ?? ""] ?? "application/octet-stream";
|
|
4230
|
+
return `data:${mime};base64,${readFileSync3(full).toString("base64")}`;
|
|
1409
4231
|
}
|
|
1410
4232
|
main().catch((e) => {
|
|
1411
4233
|
log(e instanceof Error ? e.message : e);
|