sketchmark 0.1.2 → 0.1.4
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 +461 -200
- package/dist/ast/types.d.ts +17 -0
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/index.cjs +708 -188
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +706 -189
- package/dist/index.js.map +1 -1
- package/dist/layout/index.d.ts +1 -1
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/markdown/parser.d.ts +16 -0
- package/dist/markdown/parser.d.ts.map +1 -0
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/tokenizer.d.ts +1 -1
- package/dist/parser/tokenizer.d.ts.map +1 -1
- package/dist/renderer/canvas/index.d.ts.map +1 -1
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/scene/index.d.ts +15 -0
- package/dist/scene/index.d.ts.map +1 -1
- package/dist/share/encrypted.d.ts +11 -0
- package/dist/share/encrypted.d.ts.map +1 -0
- package/dist/sketchmark.iife.js +708 -188
- package/package.json +69 -66
package/dist/sketchmark.iife.js
CHANGED
|
@@ -25,6 +25,7 @@ var AIDiagram = (function (exports) {
|
|
|
25
25
|
"step",
|
|
26
26
|
"config",
|
|
27
27
|
"theme",
|
|
28
|
+
"bare",
|
|
28
29
|
"bar-chart",
|
|
29
30
|
"line-chart",
|
|
30
31
|
"pie-chart",
|
|
@@ -64,6 +65,7 @@ var AIDiagram = (function (exports) {
|
|
|
64
65
|
"dag",
|
|
65
66
|
"tree",
|
|
66
67
|
"force",
|
|
68
|
+
"markdown",
|
|
67
69
|
]);
|
|
68
70
|
const ARROW_PATTERNS = ["<-->", "<->", "-->", "<--", "->", "<-", "---", "--"];
|
|
69
71
|
// Characters that can start an arrow pattern — used to decide whether a '-'
|
|
@@ -101,6 +103,23 @@ var AIDiagram = (function (exports) {
|
|
|
101
103
|
i++;
|
|
102
104
|
continue;
|
|
103
105
|
}
|
|
106
|
+
if (ch === '"' && peek(1) === '"' && peek(2) === '"') {
|
|
107
|
+
i += 3; // skip opening """
|
|
108
|
+
let raw = "";
|
|
109
|
+
while (i < src.length) {
|
|
110
|
+
if (src[i] === '"' && src[i + 1] === '"' && src[i + 2] === '"') {
|
|
111
|
+
i += 3; // skip closing """
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
if (src[i] === "\n") {
|
|
115
|
+
line++;
|
|
116
|
+
lineStart = i + 1;
|
|
117
|
+
}
|
|
118
|
+
raw += src[i++];
|
|
119
|
+
}
|
|
120
|
+
add("STRING_BLOCK", raw);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
104
123
|
// Strings
|
|
105
124
|
if (ch === '"' || ch === "'") {
|
|
106
125
|
const q = ch;
|
|
@@ -251,14 +270,16 @@ var AIDiagram = (function (exports) {
|
|
|
251
270
|
s.fontSize = parseFloat(p["font-size"]);
|
|
252
271
|
if (p["font-weight"])
|
|
253
272
|
s.fontWeight = p["font-weight"];
|
|
254
|
-
if (p[
|
|
255
|
-
s.textAlign = p[
|
|
256
|
-
if (p
|
|
257
|
-
s.
|
|
258
|
-
if (p[
|
|
259
|
-
s.
|
|
260
|
-
if (p[
|
|
261
|
-
s.
|
|
273
|
+
if (p["text-align"])
|
|
274
|
+
s.textAlign = p["text-align"];
|
|
275
|
+
if (p.padding)
|
|
276
|
+
s.padding = parseFloat(p.padding);
|
|
277
|
+
if (p["vertical-align"])
|
|
278
|
+
s.verticalAlign = p["vertical-align"];
|
|
279
|
+
if (p["line-height"])
|
|
280
|
+
s.lineHeight = parseFloat(p["line-height"]);
|
|
281
|
+
if (p["letter-spacing"])
|
|
282
|
+
s.letterSpacing = parseFloat(p["letter-spacing"]);
|
|
262
283
|
if (p.font)
|
|
263
284
|
s.font = p.font;
|
|
264
285
|
if (p["dash"]) {
|
|
@@ -298,6 +319,7 @@ var AIDiagram = (function (exports) {
|
|
|
298
319
|
notes: [],
|
|
299
320
|
charts: [],
|
|
300
321
|
tables: [],
|
|
322
|
+
markdowns: [],
|
|
301
323
|
styles: {},
|
|
302
324
|
themes: {},
|
|
303
325
|
config: {},
|
|
@@ -308,6 +330,7 @@ var AIDiagram = (function (exports) {
|
|
|
308
330
|
const noteIds = new Set();
|
|
309
331
|
const chartIds = new Set();
|
|
310
332
|
const groupIds = new Set();
|
|
333
|
+
const markdownIds = new Set();
|
|
311
334
|
let i = 0;
|
|
312
335
|
const cur = () => flat[i] ?? flat[flat.length - 1];
|
|
313
336
|
const peek1 = () => flat[i + 1] ?? flat[flat.length - 1];
|
|
@@ -521,7 +544,7 @@ var AIDiagram = (function (exports) {
|
|
|
521
544
|
const group = {
|
|
522
545
|
kind: "group",
|
|
523
546
|
id,
|
|
524
|
-
label: props.label ??
|
|
547
|
+
label: props.label ?? "",
|
|
525
548
|
children: [],
|
|
526
549
|
layout: props.layout,
|
|
527
550
|
columns: props.columns !== undefined ? parseInt(props.columns, 10) : undefined,
|
|
@@ -547,8 +570,18 @@ var AIDiagram = (function (exports) {
|
|
|
547
570
|
break;
|
|
548
571
|
const v = cur().value;
|
|
549
572
|
// ── Nested group ──────────────────────────────────
|
|
550
|
-
if (v === "group") {
|
|
573
|
+
if (v === "group" || v === "bare") {
|
|
574
|
+
const isBare = v === "bare";
|
|
551
575
|
const nested = parseGroup();
|
|
576
|
+
if (isBare) {
|
|
577
|
+
nested.label = "";
|
|
578
|
+
nested.style = {
|
|
579
|
+
...nested.style,
|
|
580
|
+
fill: nested.style?.fill ?? "none",
|
|
581
|
+
stroke: nested.style?.stroke ?? "none",
|
|
582
|
+
strokeWidth: nested.style?.strokeWidth ?? 0,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
552
585
|
ast.groups.push(nested);
|
|
553
586
|
groupIds.add(nested.id);
|
|
554
587
|
group.children.push({ kind: "group", id: nested.id });
|
|
@@ -570,6 +603,21 @@ var AIDiagram = (function (exports) {
|
|
|
570
603
|
group.children.push({ kind: "note", id: note.id });
|
|
571
604
|
continue;
|
|
572
605
|
}
|
|
606
|
+
// ── Markdown ───────────────────────────────────────
|
|
607
|
+
if (v === "markdown") {
|
|
608
|
+
const md = parseMarkdown(id);
|
|
609
|
+
ast.markdowns.push(md);
|
|
610
|
+
markdownIds.add(md.id);
|
|
611
|
+
group.children.push({ kind: "markdown", id: md.id });
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
if (v === "bare") {
|
|
615
|
+
// treat exactly like 'group' but inject defaults
|
|
616
|
+
const grp = parseGroup(); // reuse parseGroup
|
|
617
|
+
grp.label = "";
|
|
618
|
+
grp.style = { ...grp.style, stroke: "none", fill: "none" };
|
|
619
|
+
// rest is identical to group handling
|
|
620
|
+
}
|
|
573
621
|
// ── Chart ──────────────────────────────────────────
|
|
574
622
|
if (CHART_TYPES.includes(v)) {
|
|
575
623
|
const chart = parseChart(v);
|
|
@@ -613,71 +661,71 @@ var AIDiagram = (function (exports) {
|
|
|
613
661
|
function parseStep() {
|
|
614
662
|
skip();
|
|
615
663
|
const toks = lineTokens();
|
|
616
|
-
const action = (toks[0]?.value ??
|
|
617
|
-
let target = toks[1]?.value ??
|
|
618
|
-
if (toks[2]?.type ===
|
|
664
|
+
const action = (toks[0]?.value ?? "highlight");
|
|
665
|
+
let target = toks[1]?.value ?? "";
|
|
666
|
+
if (toks[2]?.type === "ARROW" && toks[3]) {
|
|
619
667
|
target = `${toks[1].value}${toks[2].value}${toks[3].value}`;
|
|
620
668
|
}
|
|
621
|
-
const step = { kind:
|
|
669
|
+
const step = { kind: "step", action, target };
|
|
622
670
|
for (let j = 2; j < toks.length; j++) {
|
|
623
671
|
const k = toks[j]?.value;
|
|
624
672
|
const eq = toks[j + 1];
|
|
625
673
|
const vt = toks[j + 2];
|
|
626
674
|
// key=value form
|
|
627
|
-
if (eq?.type ===
|
|
628
|
-
if (k ===
|
|
675
|
+
if (eq?.type === "EQUALS" && vt) {
|
|
676
|
+
if (k === "dx") {
|
|
629
677
|
step.dx = parseFloat(vt.value);
|
|
630
678
|
j += 2;
|
|
631
679
|
continue;
|
|
632
680
|
}
|
|
633
|
-
if (k ===
|
|
681
|
+
if (k === "dy") {
|
|
634
682
|
step.dy = parseFloat(vt.value);
|
|
635
683
|
j += 2;
|
|
636
684
|
continue;
|
|
637
685
|
}
|
|
638
|
-
if (k ===
|
|
686
|
+
if (k === "duration") {
|
|
639
687
|
step.duration = parseFloat(vt.value);
|
|
640
688
|
j += 2;
|
|
641
689
|
continue;
|
|
642
690
|
}
|
|
643
|
-
if (k ===
|
|
691
|
+
if (k === "delay") {
|
|
644
692
|
step.delay = parseFloat(vt.value);
|
|
645
693
|
j += 2;
|
|
646
694
|
continue;
|
|
647
695
|
}
|
|
648
|
-
if (k ===
|
|
696
|
+
if (k === "factor") {
|
|
649
697
|
step.factor = parseFloat(vt.value);
|
|
650
698
|
j += 2;
|
|
651
699
|
continue;
|
|
652
700
|
}
|
|
653
|
-
if (k ===
|
|
701
|
+
if (k === "deg") {
|
|
654
702
|
step.deg = parseFloat(vt.value);
|
|
655
703
|
j += 2;
|
|
656
704
|
continue;
|
|
657
705
|
}
|
|
658
|
-
if (k ===
|
|
706
|
+
if (k === "fill") {
|
|
659
707
|
step.value = vt.value;
|
|
660
708
|
j += 2;
|
|
661
709
|
continue;
|
|
662
710
|
}
|
|
663
|
-
if (k ===
|
|
711
|
+
if (k === "color") {
|
|
664
712
|
step.value = vt.value;
|
|
665
713
|
j += 2;
|
|
666
714
|
continue;
|
|
667
715
|
}
|
|
668
716
|
}
|
|
669
717
|
// bare key value (legacy)
|
|
670
|
-
if (k ===
|
|
718
|
+
if (k === "delay" && eq?.type === "NUMBER") {
|
|
671
719
|
step.delay = parseFloat(eq.value);
|
|
672
720
|
j++;
|
|
673
721
|
continue;
|
|
674
722
|
}
|
|
675
|
-
if (k ===
|
|
723
|
+
if (k === "duration" && eq?.type === "NUMBER") {
|
|
676
724
|
step.duration = parseFloat(eq.value);
|
|
677
725
|
j++;
|
|
678
726
|
continue;
|
|
679
727
|
}
|
|
680
|
-
if (k ===
|
|
728
|
+
if (k === "trigger") {
|
|
681
729
|
step.trigger = eq?.value;
|
|
682
730
|
j++;
|
|
683
731
|
continue;
|
|
@@ -877,6 +925,40 @@ var AIDiagram = (function (exports) {
|
|
|
877
925
|
skip();
|
|
878
926
|
return table;
|
|
879
927
|
}
|
|
928
|
+
// ── parseMarkdown ─────────────────────────────────────────
|
|
929
|
+
function parseMarkdown(groupId) {
|
|
930
|
+
skip(); // 'markdown'
|
|
931
|
+
const toks = lineTokens();
|
|
932
|
+
let id = groupId ? groupId + "_" + uid("md") : uid("md");
|
|
933
|
+
if (toks[0])
|
|
934
|
+
id = toks[0].value;
|
|
935
|
+
const props = {};
|
|
936
|
+
let j = 1;
|
|
937
|
+
while (j < toks.length - 1) {
|
|
938
|
+
const k = toks[j], eq = toks[j + 1];
|
|
939
|
+
if (eq?.type === "EQUALS" && j + 2 < toks.length) {
|
|
940
|
+
props[k.value] = toks[j + 2].value;
|
|
941
|
+
j += 3;
|
|
942
|
+
}
|
|
943
|
+
else
|
|
944
|
+
j++;
|
|
945
|
+
}
|
|
946
|
+
skipNL();
|
|
947
|
+
let content = "";
|
|
948
|
+
if (cur().type === "STRING_BLOCK") {
|
|
949
|
+
content = cur().value;
|
|
950
|
+
skip();
|
|
951
|
+
}
|
|
952
|
+
return {
|
|
953
|
+
kind: "markdown",
|
|
954
|
+
id,
|
|
955
|
+
content: content.trim(),
|
|
956
|
+
width: props.width ? parseFloat(props.width) : undefined,
|
|
957
|
+
height: props.height ? parseFloat(props.height) : undefined,
|
|
958
|
+
theme: props.theme,
|
|
959
|
+
style: propsToStyle(props),
|
|
960
|
+
};
|
|
961
|
+
}
|
|
880
962
|
// ── Main parse loop ─────────────────────────────────────
|
|
881
963
|
skipNL();
|
|
882
964
|
if (cur().value === "diagram")
|
|
@@ -990,8 +1072,18 @@ var AIDiagram = (function (exports) {
|
|
|
990
1072
|
continue;
|
|
991
1073
|
}
|
|
992
1074
|
// group
|
|
993
|
-
if (v === "group") {
|
|
1075
|
+
if (v === "group" || v === "bare") {
|
|
1076
|
+
const isBare = v === "bare";
|
|
994
1077
|
const grp = parseGroup();
|
|
1078
|
+
if (isBare) {
|
|
1079
|
+
grp.label = "";
|
|
1080
|
+
grp.style = {
|
|
1081
|
+
...grp.style,
|
|
1082
|
+
fill: grp.style?.fill ?? "none",
|
|
1083
|
+
stroke: grp.style?.stroke ?? "none",
|
|
1084
|
+
strokeWidth: grp.style?.strokeWidth ?? 0,
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
995
1087
|
ast.groups.push(grp);
|
|
996
1088
|
groupIds.add(grp.id);
|
|
997
1089
|
ast.rootOrder.push({ kind: "group", id: grp.id });
|
|
@@ -1026,6 +1118,13 @@ var AIDiagram = (function (exports) {
|
|
|
1026
1118
|
ast.rootOrder.push({ kind: "chart", id: chart.id }); // ← ADD
|
|
1027
1119
|
continue;
|
|
1028
1120
|
}
|
|
1121
|
+
if (v === "markdown") {
|
|
1122
|
+
const md = parseMarkdown();
|
|
1123
|
+
ast.markdowns.push(md);
|
|
1124
|
+
markdownIds.add(md.id);
|
|
1125
|
+
ast.rootOrder.push({ kind: "markdown", id: md.id });
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1029
1128
|
// edge: A -> B (MUST come before shape check)
|
|
1030
1129
|
if (t.type === "IDENT" || t.type === "STRING" || t.type === "KEYWORD") {
|
|
1031
1130
|
const nextTok = flat[i + 1];
|
|
@@ -1078,6 +1177,91 @@ var AIDiagram = (function (exports) {
|
|
|
1078
1177
|
return ast;
|
|
1079
1178
|
}
|
|
1080
1179
|
|
|
1180
|
+
// ============================================================
|
|
1181
|
+
// sketchmark — Markdown inline parser
|
|
1182
|
+
// Supports: # h1 ## h2 ### h3 **bold** *italic* blank lines
|
|
1183
|
+
// ============================================================
|
|
1184
|
+
// ── Font sizes per line kind ──────────────────────────────
|
|
1185
|
+
const LINE_FONT_SIZE = {
|
|
1186
|
+
h1: 40,
|
|
1187
|
+
h2: 28,
|
|
1188
|
+
h3: 20,
|
|
1189
|
+
p: 15,
|
|
1190
|
+
blank: 0,
|
|
1191
|
+
};
|
|
1192
|
+
const LINE_FONT_WEIGHT = {
|
|
1193
|
+
h1: 700,
|
|
1194
|
+
h2: 600,
|
|
1195
|
+
h3: 600,
|
|
1196
|
+
p: 400,
|
|
1197
|
+
blank: 400,
|
|
1198
|
+
};
|
|
1199
|
+
// Spacing below each line kind (px)
|
|
1200
|
+
const LINE_SPACING = {
|
|
1201
|
+
h1: 52,
|
|
1202
|
+
h2: 38,
|
|
1203
|
+
h3: 28,
|
|
1204
|
+
p: 22,
|
|
1205
|
+
blank: 10,
|
|
1206
|
+
};
|
|
1207
|
+
// ── Parse a full markdown string into lines ───────────────
|
|
1208
|
+
function parseMarkdownContent(content) {
|
|
1209
|
+
const raw = content.split('\n');
|
|
1210
|
+
const lines = [];
|
|
1211
|
+
for (const line of raw) {
|
|
1212
|
+
const t = line.trim();
|
|
1213
|
+
if (!t) {
|
|
1214
|
+
lines.push({ kind: 'blank', runs: [] });
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
if (t.startsWith('### ')) {
|
|
1218
|
+
lines.push({ kind: 'h3', runs: parseInline(t.slice(4)) });
|
|
1219
|
+
}
|
|
1220
|
+
else if (t.startsWith('## ')) {
|
|
1221
|
+
lines.push({ kind: 'h2', runs: parseInline(t.slice(3)) });
|
|
1222
|
+
}
|
|
1223
|
+
else if (t.startsWith('# ')) {
|
|
1224
|
+
lines.push({ kind: 'h1', runs: parseInline(t.slice(2)) });
|
|
1225
|
+
}
|
|
1226
|
+
else {
|
|
1227
|
+
lines.push({ kind: 'p', runs: parseInline(t) });
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
// strip leading/trailing blank lines
|
|
1231
|
+
while (lines.length && lines[0].kind === 'blank')
|
|
1232
|
+
lines.shift();
|
|
1233
|
+
while (lines.length && lines[lines.length - 1].kind === 'blank')
|
|
1234
|
+
lines.pop();
|
|
1235
|
+
return lines;
|
|
1236
|
+
}
|
|
1237
|
+
// ── Parse inline bold/italic spans ───────────────────────
|
|
1238
|
+
function parseInline(text) {
|
|
1239
|
+
const runs = [];
|
|
1240
|
+
// Order matters: check ** before *
|
|
1241
|
+
const re = /(\*\*(.+?)\*\*|\*(.+?)\*|[^*]+)/g;
|
|
1242
|
+
let m;
|
|
1243
|
+
while ((m = re.exec(text)) !== null) {
|
|
1244
|
+
if (m[0].startsWith('**')) {
|
|
1245
|
+
runs.push({ text: m[2], bold: true });
|
|
1246
|
+
}
|
|
1247
|
+
else if (m[0].startsWith('*')) {
|
|
1248
|
+
runs.push({ text: m[3], italic: true });
|
|
1249
|
+
}
|
|
1250
|
+
else {
|
|
1251
|
+
if (m[0])
|
|
1252
|
+
runs.push({ text: m[0] });
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return runs;
|
|
1256
|
+
}
|
|
1257
|
+
// ── Calculate natural height of a parsed block ────────────
|
|
1258
|
+
function calcMarkdownHeight(lines, pad = 16) {
|
|
1259
|
+
let h = pad * 2; // top + bottom
|
|
1260
|
+
for (const line of lines)
|
|
1261
|
+
h += LINE_SPACING[line.kind];
|
|
1262
|
+
return h;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1081
1265
|
// ============================================================
|
|
1082
1266
|
// sketchmark — Scene Graph
|
|
1083
1267
|
// ============================================================
|
|
@@ -1168,6 +1352,18 @@ var AIDiagram = (function (exports) {
|
|
|
1168
1352
|
h: c.height ?? 240,
|
|
1169
1353
|
};
|
|
1170
1354
|
});
|
|
1355
|
+
const markdowns = (ast.markdowns ?? []).map(m => {
|
|
1356
|
+
const themeStyle = m.theme ? (ast.themes[m.theme] ?? {}) : {};
|
|
1357
|
+
return {
|
|
1358
|
+
id: m.id,
|
|
1359
|
+
content: m.content,
|
|
1360
|
+
lines: parseMarkdownContent(m.content),
|
|
1361
|
+
style: { ...themeStyle, ...m.style },
|
|
1362
|
+
width: m.width,
|
|
1363
|
+
height: m.height,
|
|
1364
|
+
x: 0, y: 0, w: 0, h: 0,
|
|
1365
|
+
};
|
|
1366
|
+
});
|
|
1171
1367
|
// Set parentId for nested groups
|
|
1172
1368
|
for (const g of groups) {
|
|
1173
1369
|
for (const child of g.children) {
|
|
@@ -1198,6 +1394,7 @@ var AIDiagram = (function (exports) {
|
|
|
1198
1394
|
tables,
|
|
1199
1395
|
notes,
|
|
1200
1396
|
charts,
|
|
1397
|
+
markdowns,
|
|
1201
1398
|
animation: { steps: ast.steps, currentStep: -1 },
|
|
1202
1399
|
styles: ast.styles,
|
|
1203
1400
|
config: ast.config,
|
|
@@ -1222,6 +1419,9 @@ var AIDiagram = (function (exports) {
|
|
|
1222
1419
|
function chartMap(sg) {
|
|
1223
1420
|
return new Map(sg.charts.map((c) => [c.id, c]));
|
|
1224
1421
|
}
|
|
1422
|
+
function markdownMap(sg) {
|
|
1423
|
+
return new Map((sg.markdowns ?? []).map(m => [m.id, m]));
|
|
1424
|
+
}
|
|
1225
1425
|
|
|
1226
1426
|
// ============================================================
|
|
1227
1427
|
// sketchmark — Layout Engine (Flexbox-style, recursive)
|
|
@@ -1262,30 +1462,40 @@ var AIDiagram = (function (exports) {
|
|
|
1262
1462
|
n.h = n.height;
|
|
1263
1463
|
const labelW = Math.round(n.label.length * FONT_PX_PER_CHAR + BASE_PAD);
|
|
1264
1464
|
switch (n.shape) {
|
|
1265
|
-
case
|
|
1465
|
+
case "circle":
|
|
1266
1466
|
n.w = n.w || Math.max(84, Math.min(MAX_W, labelW));
|
|
1267
1467
|
n.h = n.h || n.w;
|
|
1268
1468
|
break;
|
|
1269
|
-
case
|
|
1469
|
+
case "diamond":
|
|
1270
1470
|
n.w = n.w || Math.max(130, Math.min(MAX_W, labelW + 30));
|
|
1271
1471
|
n.h = n.h || Math.max(62, n.w * 0.46);
|
|
1272
1472
|
break;
|
|
1273
|
-
case
|
|
1473
|
+
case "hexagon":
|
|
1274
1474
|
n.w = n.w || Math.max(126, Math.min(MAX_W, labelW + 20));
|
|
1275
1475
|
n.h = n.h || Math.max(54, n.w * 0.44);
|
|
1276
1476
|
break;
|
|
1277
|
-
case
|
|
1477
|
+
case "triangle":
|
|
1278
1478
|
n.w = n.w || Math.max(108, Math.min(MAX_W, labelW + 10));
|
|
1279
|
-
n.h = n.h || Math.max(64, n.w * 0.
|
|
1479
|
+
n.h = n.h || Math.max(64, n.w * 0.6);
|
|
1280
1480
|
break;
|
|
1281
|
-
case
|
|
1481
|
+
case "cylinder":
|
|
1282
1482
|
n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW));
|
|
1283
1483
|
n.h = n.h || 66;
|
|
1284
1484
|
break;
|
|
1285
|
-
case
|
|
1485
|
+
case "parallelogram":
|
|
1286
1486
|
n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW + 28));
|
|
1287
1487
|
n.h = n.h || 50;
|
|
1288
1488
|
break;
|
|
1489
|
+
case "text": {
|
|
1490
|
+
// read fontSize from style if set, otherwise use default
|
|
1491
|
+
const fontSize = Number(n.style?.fontSize ?? 13);
|
|
1492
|
+
const charWidth = fontSize * 0.55;
|
|
1493
|
+
const maxW = n.width ?? 400;
|
|
1494
|
+
const approxLines = Math.ceil((n.label.length * charWidth) / (maxW - 16));
|
|
1495
|
+
n.w = maxW;
|
|
1496
|
+
n.h = n.height ?? Math.max(24, approxLines * fontSize * 1.5 + 8);
|
|
1497
|
+
break;
|
|
1498
|
+
}
|
|
1289
1499
|
default:
|
|
1290
1500
|
n.w = n.w || Math.max(MIN_W, Math.min(MAX_W, labelW));
|
|
1291
1501
|
n.h = n.h || 52;
|
|
@@ -1293,7 +1503,7 @@ var AIDiagram = (function (exports) {
|
|
|
1293
1503
|
}
|
|
1294
1504
|
}
|
|
1295
1505
|
function sizeNote(n) {
|
|
1296
|
-
const maxChars = Math.max(...n.lines.map(l => l.length));
|
|
1506
|
+
const maxChars = Math.max(...n.lines.map((l) => l.length));
|
|
1297
1507
|
n.w = Math.max(120, Math.ceil(maxChars * NOTE_FONT) + NOTE_PAD_X * 2);
|
|
1298
1508
|
n.h = n.lines.length * NOTE_LINE_H + NOTE_PAD_Y * 2;
|
|
1299
1509
|
if (n.width && n.w < n.width)
|
|
@@ -1309,7 +1519,7 @@ var AIDiagram = (function (exports) {
|
|
|
1309
1519
|
t.h = labelH + rowH;
|
|
1310
1520
|
return;
|
|
1311
1521
|
}
|
|
1312
|
-
const numCols = Math.max(...rows.map(r => r.cells.length));
|
|
1522
|
+
const numCols = Math.max(...rows.map((r) => r.cells.length));
|
|
1313
1523
|
const colW = Array(numCols).fill(MIN_COL_W);
|
|
1314
1524
|
for (const row of rows) {
|
|
1315
1525
|
row.cells.forEach((cell, i) => {
|
|
@@ -1318,110 +1528,128 @@ var AIDiagram = (function (exports) {
|
|
|
1318
1528
|
}
|
|
1319
1529
|
t.colWidths = colW;
|
|
1320
1530
|
t.w = colW.reduce((s, w) => s + w, 0);
|
|
1321
|
-
const nHeader = rows.filter(r => r.kind ===
|
|
1322
|
-
const nData = rows.filter(r => r.kind ===
|
|
1531
|
+
const nHeader = rows.filter((r) => r.kind === "header").length;
|
|
1532
|
+
const nData = rows.filter((r) => r.kind === "data").length;
|
|
1323
1533
|
t.h = labelH + nHeader * headerH + nData * rowH;
|
|
1324
1534
|
}
|
|
1325
1535
|
function sizeChart(c) {
|
|
1326
1536
|
c.w = c.w || 320;
|
|
1327
1537
|
c.h = c.h || 240;
|
|
1328
1538
|
}
|
|
1539
|
+
function sizeMarkdown(m) {
|
|
1540
|
+
const pad = Number(m.style?.padding ?? 16);
|
|
1541
|
+
m.w = m.width ?? 400;
|
|
1542
|
+
m.h = m.height ?? calcMarkdownHeight(m.lines, pad);
|
|
1543
|
+
}
|
|
1329
1544
|
// ── Item size helpers ─────────────────────────────────────
|
|
1330
|
-
function iW(r, nm, gm, tm, ntm, cm) {
|
|
1331
|
-
if (r.kind ===
|
|
1545
|
+
function iW(r, nm, gm, tm, ntm, cm, mdm) {
|
|
1546
|
+
if (r.kind === "node")
|
|
1332
1547
|
return nm.get(r.id).w;
|
|
1333
|
-
if (r.kind ===
|
|
1548
|
+
if (r.kind === "table")
|
|
1334
1549
|
return tm.get(r.id).w;
|
|
1335
|
-
if (r.kind ===
|
|
1550
|
+
if (r.kind === "note")
|
|
1336
1551
|
return ntm.get(r.id).w;
|
|
1337
|
-
if (r.kind ===
|
|
1552
|
+
if (r.kind === "chart")
|
|
1338
1553
|
return cm.get(r.id).w;
|
|
1554
|
+
if (r.kind === "markdown")
|
|
1555
|
+
return mdm.get(r.id).w;
|
|
1339
1556
|
return gm.get(r.id).w;
|
|
1340
1557
|
}
|
|
1341
|
-
function iH(r, nm, gm, tm, ntm, cm) {
|
|
1342
|
-
if (r.kind ===
|
|
1558
|
+
function iH(r, nm, gm, tm, ntm, cm, mdm) {
|
|
1559
|
+
if (r.kind === "node")
|
|
1343
1560
|
return nm.get(r.id).h;
|
|
1344
|
-
if (r.kind ===
|
|
1561
|
+
if (r.kind === "table")
|
|
1345
1562
|
return tm.get(r.id).h;
|
|
1346
|
-
if (r.kind ===
|
|
1563
|
+
if (r.kind === "note")
|
|
1347
1564
|
return ntm.get(r.id).h;
|
|
1348
|
-
if (r.kind ===
|
|
1565
|
+
if (r.kind === "chart")
|
|
1349
1566
|
return cm.get(r.id).h;
|
|
1567
|
+
if (r.kind === "markdown")
|
|
1568
|
+
return mdm.get(r.id).h;
|
|
1350
1569
|
return gm.get(r.id).h;
|
|
1351
1570
|
}
|
|
1352
|
-
function setPos(r, x, y, nm, gm, tm, ntm, cm) {
|
|
1353
|
-
if (r.kind ===
|
|
1571
|
+
function setPos(r, x, y, nm, gm, tm, ntm, cm, mdm) {
|
|
1572
|
+
if (r.kind === "node") {
|
|
1354
1573
|
const n = nm.get(r.id);
|
|
1355
1574
|
n.x = Math.round(x);
|
|
1356
1575
|
n.y = Math.round(y);
|
|
1357
1576
|
return;
|
|
1358
1577
|
}
|
|
1359
|
-
if (r.kind ===
|
|
1578
|
+
if (r.kind === "table") {
|
|
1360
1579
|
const t = tm.get(r.id);
|
|
1361
1580
|
t.x = Math.round(x);
|
|
1362
1581
|
t.y = Math.round(y);
|
|
1363
1582
|
return;
|
|
1364
1583
|
}
|
|
1365
|
-
if (r.kind ===
|
|
1584
|
+
if (r.kind === "note") {
|
|
1366
1585
|
const nt = ntm.get(r.id);
|
|
1367
1586
|
nt.x = Math.round(x);
|
|
1368
1587
|
nt.y = Math.round(y);
|
|
1369
1588
|
return;
|
|
1370
1589
|
}
|
|
1371
|
-
if (r.kind ===
|
|
1590
|
+
if (r.kind === "chart") {
|
|
1372
1591
|
const c = cm.get(r.id);
|
|
1373
1592
|
c.x = Math.round(x);
|
|
1374
1593
|
c.y = Math.round(y);
|
|
1375
1594
|
return;
|
|
1376
1595
|
}
|
|
1596
|
+
if (r.kind === "markdown") {
|
|
1597
|
+
const md = mdm.get(r.id);
|
|
1598
|
+
md.x = Math.round(x);
|
|
1599
|
+
md.y = Math.round(y);
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1377
1602
|
const g = gm.get(r.id);
|
|
1378
1603
|
g.x = Math.round(x);
|
|
1379
1604
|
g.y = Math.round(y);
|
|
1380
1605
|
}
|
|
1381
1606
|
// ── Pass 1: Measure (bottom-up) ───────────────────────────
|
|
1382
1607
|
// Recursively computes w, h for a group from its children's sizes.
|
|
1383
|
-
function measure(g, nm, gm, tm, ntm, cm) {
|
|
1608
|
+
function measure(g, nm, gm, tm, ntm, cm, mdm) {
|
|
1384
1609
|
// Recurse into nested groups first; size tables before reading their dims
|
|
1385
1610
|
for (const r of g.children) {
|
|
1386
|
-
if (r.kind ===
|
|
1387
|
-
measure(gm.get(r.id), nm, gm, tm, ntm, cm);
|
|
1388
|
-
if (r.kind ===
|
|
1611
|
+
if (r.kind === "group")
|
|
1612
|
+
measure(gm.get(r.id), nm, gm, tm, ntm, cm, mdm);
|
|
1613
|
+
if (r.kind === "table")
|
|
1389
1614
|
sizeTable(tm.get(r.id));
|
|
1390
|
-
if (r.kind ===
|
|
1615
|
+
if (r.kind === "note")
|
|
1391
1616
|
sizeNote(ntm.get(r.id));
|
|
1392
|
-
if (r.kind ===
|
|
1617
|
+
if (r.kind === "chart")
|
|
1393
1618
|
sizeChart(cm.get(r.id));
|
|
1619
|
+
if (r.kind === "markdown")
|
|
1620
|
+
sizeMarkdown(mdm.get(r.id));
|
|
1394
1621
|
}
|
|
1395
1622
|
const { padding: pad, gap, columns, layout } = g;
|
|
1396
1623
|
const kids = g.children;
|
|
1624
|
+
const labelH = g.label ? GROUP_LABEL_H : 0;
|
|
1397
1625
|
if (!kids.length) {
|
|
1398
1626
|
g.w = pad * 2;
|
|
1399
|
-
g.h = pad * 2 +
|
|
1627
|
+
g.h = pad * 2 + labelH;
|
|
1400
1628
|
if (g.width && g.w < g.width)
|
|
1401
1629
|
g.w = g.width;
|
|
1402
1630
|
if (g.height && g.h < g.height)
|
|
1403
1631
|
g.h = g.height;
|
|
1404
1632
|
return;
|
|
1405
1633
|
}
|
|
1406
|
-
const ws = kids.map(r => iW(r, nm, gm, tm, ntm, cm));
|
|
1407
|
-
const hs = kids.map(r => iH(r, nm, gm, tm, ntm, cm));
|
|
1634
|
+
const ws = kids.map((r) => iW(r, nm, gm, tm, ntm, cm, mdm));
|
|
1635
|
+
const hs = kids.map((r) => iH(r, nm, gm, tm, ntm, cm, mdm));
|
|
1408
1636
|
const n = kids.length;
|
|
1409
|
-
if (layout ===
|
|
1637
|
+
if (layout === "row") {
|
|
1410
1638
|
g.w = ws.reduce((s, w) => s + w, 0) + gap * (n - 1) + pad * 2;
|
|
1411
|
-
g.h = Math.max(...hs) + pad * 2 +
|
|
1639
|
+
g.h = Math.max(...hs) + pad * 2 + labelH;
|
|
1412
1640
|
}
|
|
1413
|
-
else if (layout ===
|
|
1641
|
+
else if (layout === "grid") {
|
|
1414
1642
|
const cols = Math.max(1, columns);
|
|
1415
1643
|
const rows = Math.ceil(n / cols);
|
|
1416
1644
|
const cellW = Math.max(...ws);
|
|
1417
1645
|
const cellH = Math.max(...hs);
|
|
1418
1646
|
g.w = cols * cellW + (cols - 1) * gap + pad * 2;
|
|
1419
|
-
g.h = rows * cellH + (rows - 1) * gap + pad * 2 +
|
|
1647
|
+
g.h = rows * cellH + (rows - 1) * gap + pad * 2 + labelH;
|
|
1420
1648
|
}
|
|
1421
1649
|
else {
|
|
1422
1650
|
// column (default)
|
|
1423
1651
|
g.w = Math.max(...ws) + pad * 2;
|
|
1424
|
-
g.h = hs.reduce((s, h) => s + h, 0) + gap * (n - 1) + pad * 2 +
|
|
1652
|
+
g.h = hs.reduce((s, h) => s + h, 0) + gap * (n - 1) + pad * 2 + labelH;
|
|
1425
1653
|
}
|
|
1426
1654
|
// Clamp to minWidth / minHeight — this is what gives distribute() free
|
|
1427
1655
|
// space to work with for justify=center/end/space-between/space-around
|
|
@@ -1436,21 +1664,32 @@ var AIDiagram = (function (exports) {
|
|
|
1436
1664
|
const totalSize = sizes.reduce((s, v) => s + v, 0);
|
|
1437
1665
|
const gapCount = n - 1;
|
|
1438
1666
|
switch (justify) {
|
|
1439
|
-
case
|
|
1667
|
+
case "center": {
|
|
1440
1668
|
const total = totalSize + gap * gapCount;
|
|
1441
|
-
return {
|
|
1669
|
+
return {
|
|
1670
|
+
start: Math.max(0, (contentSize - total) / 2),
|
|
1671
|
+
gaps: Array(gapCount).fill(gap),
|
|
1672
|
+
};
|
|
1442
1673
|
}
|
|
1443
|
-
case
|
|
1674
|
+
case "end": {
|
|
1444
1675
|
const total = totalSize + gap * gapCount;
|
|
1445
|
-
return {
|
|
1676
|
+
return {
|
|
1677
|
+
start: Math.max(0, contentSize - total),
|
|
1678
|
+
gaps: Array(gapCount).fill(gap),
|
|
1679
|
+
};
|
|
1446
1680
|
}
|
|
1447
|
-
case
|
|
1448
|
-
const g2 = gapCount > 0
|
|
1681
|
+
case "space-between": {
|
|
1682
|
+
const g2 = gapCount > 0
|
|
1683
|
+
? Math.max(gap, (contentSize - totalSize) / gapCount)
|
|
1684
|
+
: gap;
|
|
1449
1685
|
return { start: 0, gaps: Array(gapCount).fill(g2) };
|
|
1450
1686
|
}
|
|
1451
|
-
case
|
|
1687
|
+
case "space-around": {
|
|
1452
1688
|
const space = n > 0 ? (contentSize - totalSize) / n : gap;
|
|
1453
|
-
return {
|
|
1689
|
+
return {
|
|
1690
|
+
start: Math.max(0, space / 2),
|
|
1691
|
+
gaps: Array(gapCount).fill(Math.max(gap, space)),
|
|
1692
|
+
};
|
|
1454
1693
|
}
|
|
1455
1694
|
default: // start
|
|
1456
1695
|
return { start: 0, gaps: Array(gapCount).fill(gap) };
|
|
@@ -1458,70 +1697,73 @@ var AIDiagram = (function (exports) {
|
|
|
1458
1697
|
}
|
|
1459
1698
|
// ── Pass 2: Place (top-down) ──────────────────────────────
|
|
1460
1699
|
// Assigns x, y to each child. Assumes g.x / g.y already set by parent.
|
|
1461
|
-
function place(g, nm, gm, tm, ntm, cm) {
|
|
1700
|
+
function place(g, nm, gm, tm, ntm, cm, mdm) {
|
|
1462
1701
|
const { padding: pad, gap, columns, layout, align, justify } = g;
|
|
1702
|
+
const labelH = g.label ? GROUP_LABEL_H : 0;
|
|
1463
1703
|
const contentX = g.x + pad;
|
|
1464
|
-
const contentY = g.y +
|
|
1704
|
+
const contentY = g.y + labelH + pad;
|
|
1465
1705
|
const contentW = g.w - pad * 2;
|
|
1466
|
-
const contentH = g.h - pad * 2 -
|
|
1706
|
+
const contentH = g.h - pad * 2 - labelH;
|
|
1467
1707
|
const kids = g.children;
|
|
1468
1708
|
if (!kids.length)
|
|
1469
1709
|
return;
|
|
1470
|
-
if (layout ===
|
|
1471
|
-
const ws = kids.map(r => iW(r, nm, gm, tm, ntm, cm));
|
|
1472
|
-
const hs = kids.map(r => iH(r, nm, gm, tm, ntm, cm));
|
|
1710
|
+
if (layout === "row") {
|
|
1711
|
+
const ws = kids.map((r) => iW(r, nm, gm, tm, ntm, cm, mdm));
|
|
1712
|
+
const hs = kids.map((r) => iH(r, nm, gm, tm, ntm, cm, mdm));
|
|
1473
1713
|
const maxH = Math.max(...hs);
|
|
1474
1714
|
const { start, gaps } = distribute(ws, contentW, gap, justify);
|
|
1475
1715
|
let x = contentX + start;
|
|
1476
1716
|
for (let i = 0; i < kids.length; i++) {
|
|
1477
1717
|
let y;
|
|
1478
1718
|
switch (align) {
|
|
1479
|
-
case
|
|
1719
|
+
case "center":
|
|
1480
1720
|
y = contentY + (maxH - hs[i]) / 2;
|
|
1481
1721
|
break;
|
|
1482
|
-
case
|
|
1722
|
+
case "end":
|
|
1483
1723
|
y = contentY + maxH - hs[i];
|
|
1484
1724
|
break;
|
|
1485
|
-
default:
|
|
1725
|
+
default:
|
|
1726
|
+
y = contentY;
|
|
1486
1727
|
}
|
|
1487
|
-
setPos(kids[i], x, y, nm, gm, tm, ntm, cm);
|
|
1728
|
+
setPos(kids[i], x, y, nm, gm, tm, ntm, cm, mdm);
|
|
1488
1729
|
x += ws[i] + (i < gaps.length ? gaps[i] : 0);
|
|
1489
1730
|
}
|
|
1490
1731
|
}
|
|
1491
|
-
else if (layout ===
|
|
1732
|
+
else if (layout === "grid") {
|
|
1492
1733
|
const cols = Math.max(1, columns);
|
|
1493
|
-
const cellW = Math.max(...kids.map(r => iW(r, nm, gm, tm, ntm, cm)));
|
|
1494
|
-
const cellH = Math.max(...kids.map(r => iH(r, nm, gm, tm, ntm, cm)));
|
|
1734
|
+
const cellW = Math.max(...kids.map((r) => iW(r, nm, gm, tm, ntm, cm, mdm)));
|
|
1735
|
+
const cellH = Math.max(...kids.map((r) => iH(r, nm, gm, tm, ntm, cm, mdm)));
|
|
1495
1736
|
kids.forEach((ref, i) => {
|
|
1496
|
-
setPos(ref, contentX + (i % cols) * (cellW + gap), contentY + Math.floor(i / cols) * (cellH + gap), nm, gm, tm, ntm, cm);
|
|
1737
|
+
setPos(ref, contentX + (i % cols) * (cellW + gap), contentY + Math.floor(i / cols) * (cellH + gap), nm, gm, tm, ntm, cm, mdm);
|
|
1497
1738
|
});
|
|
1498
1739
|
}
|
|
1499
1740
|
else {
|
|
1500
1741
|
// column (default)
|
|
1501
|
-
const ws = kids.map(r => iW(r, nm, gm, tm, ntm, cm));
|
|
1502
|
-
const hs = kids.map(r => iH(r, nm, gm, tm, ntm, cm));
|
|
1742
|
+
const ws = kids.map((r) => iW(r, nm, gm, tm, ntm, cm, mdm));
|
|
1743
|
+
const hs = kids.map((r) => iH(r, nm, gm, tm, ntm, cm, mdm));
|
|
1503
1744
|
const maxW = Math.max(...ws);
|
|
1504
1745
|
const { start, gaps } = distribute(hs, contentH, gap, justify);
|
|
1505
1746
|
let y = contentY + start;
|
|
1506
1747
|
for (let i = 0; i < kids.length; i++) {
|
|
1507
1748
|
let x;
|
|
1508
1749
|
switch (align) {
|
|
1509
|
-
case
|
|
1750
|
+
case "center":
|
|
1510
1751
|
x = contentX + (maxW - ws[i]) / 2;
|
|
1511
1752
|
break;
|
|
1512
|
-
case
|
|
1753
|
+
case "end":
|
|
1513
1754
|
x = contentX + maxW - ws[i];
|
|
1514
1755
|
break;
|
|
1515
|
-
default:
|
|
1756
|
+
default:
|
|
1757
|
+
x = contentX;
|
|
1516
1758
|
}
|
|
1517
|
-
setPos(kids[i], x, y, nm, gm, tm, ntm, cm);
|
|
1759
|
+
setPos(kids[i], x, y, nm, gm, tm, ntm, cm, mdm);
|
|
1518
1760
|
y += hs[i] + (i < gaps.length ? gaps[i] : 0);
|
|
1519
1761
|
}
|
|
1520
1762
|
}
|
|
1521
1763
|
// Recurse into nested groups
|
|
1522
1764
|
for (const r of kids) {
|
|
1523
|
-
if (r.kind ===
|
|
1524
|
-
place(gm.get(r.id), nm, gm, tm, ntm, cm);
|
|
1765
|
+
if (r.kind === "group")
|
|
1766
|
+
place(gm.get(r.id), nm, gm, tm, ntm, cm, mdm);
|
|
1525
1767
|
}
|
|
1526
1768
|
}
|
|
1527
1769
|
// ── Edge routing ──────────────────────────────────────────
|
|
@@ -1531,9 +1773,9 @@ var AIDiagram = (function (exports) {
|
|
|
1531
1773
|
const dx = ox - cx, dy = oy - cy;
|
|
1532
1774
|
if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01)
|
|
1533
1775
|
return [cx, cy];
|
|
1534
|
-
if (n.shape ===
|
|
1776
|
+
if (n.shape === "circle") {
|
|
1535
1777
|
const r = n.w * 0.44, len = Math.sqrt(dx * dx + dy * dy);
|
|
1536
|
-
return [cx + dx / len * r, cy + dy / len * r];
|
|
1778
|
+
return [cx + (dx / len) * r, cy + (dy / len) * r];
|
|
1537
1779
|
}
|
|
1538
1780
|
const hw = n.w / 2 - 2, hh = n.h / 2 - 2;
|
|
1539
1781
|
const tx = Math.abs(dx) > 0.01 ? hw / Math.abs(dx) : 1e9;
|
|
@@ -1578,9 +1820,12 @@ var AIDiagram = (function (exports) {
|
|
|
1578
1820
|
}
|
|
1579
1821
|
function connPt(src, dstCX, dstCY) {
|
|
1580
1822
|
// SceneNode has a .shape field; use the existing connPoint for it
|
|
1581
|
-
if (
|
|
1823
|
+
if ("shape" in src && src.shape) {
|
|
1582
1824
|
return connPoint(src, {
|
|
1583
|
-
x: dstCX - 1,
|
|
1825
|
+
x: dstCX - 1,
|
|
1826
|
+
y: dstCY - 1,
|
|
1827
|
+
w: 2,
|
|
1828
|
+
h: 2});
|
|
1584
1829
|
}
|
|
1585
1830
|
return rectConnPoint$2(src.x, src.y, src.w, src.h, dstCX, dstCY);
|
|
1586
1831
|
}
|
|
@@ -1593,84 +1838,84 @@ var AIDiagram = (function (exports) {
|
|
|
1593
1838
|
}
|
|
1594
1839
|
const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
|
|
1595
1840
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
1596
|
-
e.points = [
|
|
1597
|
-
connPt(src, dstCX, dstCY),
|
|
1598
|
-
connPt(dst, srcCX, srcCY),
|
|
1599
|
-
];
|
|
1841
|
+
e.points = [connPt(src, dstCX, dstCY), connPt(dst, srcCX, srcCY)];
|
|
1600
1842
|
}
|
|
1601
1843
|
}
|
|
1602
1844
|
function computeBounds(sg, margin) {
|
|
1603
1845
|
const allX = [
|
|
1604
|
-
...sg.nodes.map(n => n.x + n.w),
|
|
1605
|
-
...sg.groups.filter(g => g.w).map(g => g.x + g.w),
|
|
1606
|
-
...sg.tables.map(t => t.x + t.w),
|
|
1607
|
-
...sg.notes.map(n => n.x + n.w),
|
|
1608
|
-
...sg.charts.map(c => c.x + c.w)
|
|
1846
|
+
...sg.nodes.map((n) => n.x + n.w),
|
|
1847
|
+
...sg.groups.filter((g) => g.w).map((g) => g.x + g.w),
|
|
1848
|
+
...sg.tables.map((t) => t.x + t.w),
|
|
1849
|
+
...sg.notes.map((n) => n.x + n.w),
|
|
1850
|
+
...sg.charts.map((c) => c.x + c.w),
|
|
1851
|
+
...sg.markdowns.map((m) => m.x + m.w),
|
|
1609
1852
|
];
|
|
1610
1853
|
const allY = [
|
|
1611
|
-
...sg.nodes.map(n => n.y + n.h),
|
|
1612
|
-
...sg.groups.filter(g => g.h).map(g => g.y + g.h),
|
|
1613
|
-
...sg.tables.map(t => t.y + t.h),
|
|
1614
|
-
...sg.notes.map(n => n.y + n.h),
|
|
1615
|
-
...sg.charts.map(c => c.y + c.h)
|
|
1854
|
+
...sg.nodes.map((n) => n.y + n.h),
|
|
1855
|
+
...sg.groups.filter((g) => g.h).map((g) => g.y + g.h),
|
|
1856
|
+
...sg.tables.map((t) => t.y + t.h),
|
|
1857
|
+
...sg.notes.map((n) => n.y + n.h),
|
|
1858
|
+
...sg.charts.map((c) => c.y + c.h),
|
|
1859
|
+
...sg.markdowns.map((m) => m.y + m.h),
|
|
1616
1860
|
];
|
|
1617
1861
|
sg.width = (allX.length ? Math.max(...allX) : 400) + margin;
|
|
1618
1862
|
sg.height = (allY.length ? Math.max(...allY) : 300) + margin;
|
|
1619
1863
|
}
|
|
1620
1864
|
// ── Public entry point ────────────────────────────────────
|
|
1621
1865
|
function layout(sg) {
|
|
1622
|
-
const GAP_MAIN = Number(sg.config[
|
|
1623
|
-
const MARGIN = Number(sg.config[
|
|
1866
|
+
const GAP_MAIN = Number(sg.config["gap"] ?? DEFAULT_GAP_MAIN);
|
|
1867
|
+
const MARGIN = Number(sg.config["margin"] ?? DEFAULT_MARGIN);
|
|
1624
1868
|
const nm = nodeMap(sg);
|
|
1625
1869
|
const gm = groupMap(sg);
|
|
1626
1870
|
const tm = tableMap(sg);
|
|
1627
1871
|
const ntm = noteMap(sg);
|
|
1628
1872
|
const cm = chartMap(sg);
|
|
1629
|
-
|
|
1630
|
-
console.log('[layout] sg.rootOrder:', sg.rootOrder.map(r => r.kind + ':' + r.id));
|
|
1873
|
+
const mdm = markdownMap(sg);
|
|
1631
1874
|
// 1. Size all nodes and tables
|
|
1632
1875
|
sg.nodes.forEach(sizeNode);
|
|
1633
1876
|
sg.tables.forEach(sizeTable);
|
|
1634
1877
|
sg.notes.forEach(sizeNote);
|
|
1635
1878
|
sg.charts.forEach(sizeChart);
|
|
1879
|
+
sg.markdowns.forEach(sizeMarkdown);
|
|
1636
1880
|
// src/layout/index.ts — after sg.charts.forEach(sizeChart);
|
|
1637
1881
|
// 2. Identify root vs nested items
|
|
1638
|
-
const nestedGroupIds = new Set(sg.groups.flatMap(g => g.children.filter(c => c.kind ===
|
|
1639
|
-
const groupedNodeIds = new Set(sg.groups.flatMap(g => g.children.filter(c => c.kind ===
|
|
1640
|
-
const groupedTableIds = new Set(sg.groups.flatMap(g => g.children.filter(c => c.kind ===
|
|
1641
|
-
const groupedNoteIds = new Set(sg.groups.flatMap(g => g.children.filter(c => c.kind ===
|
|
1642
|
-
const groupedChartIds = new Set(sg.groups.flatMap(g => g.children.filter(c => c.kind ===
|
|
1643
|
-
const
|
|
1644
|
-
const
|
|
1645
|
-
const
|
|
1646
|
-
const
|
|
1647
|
-
const
|
|
1882
|
+
const nestedGroupIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "group").map((c) => c.id)));
|
|
1883
|
+
const groupedNodeIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "node").map((c) => c.id)));
|
|
1884
|
+
const groupedTableIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "table").map((c) => c.id)));
|
|
1885
|
+
const groupedNoteIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "note").map((c) => c.id)));
|
|
1886
|
+
const groupedChartIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "chart").map((c) => c.id)));
|
|
1887
|
+
const groupedMarkdownIds = new Set(sg.groups.flatMap((g) => g.children.filter((c) => c.kind === "markdown").map((c) => c.id)));
|
|
1888
|
+
const rootGroups = sg.groups.filter((g) => !nestedGroupIds.has(g.id));
|
|
1889
|
+
const rootNodes = sg.nodes.filter((n) => !groupedNodeIds.has(n.id));
|
|
1890
|
+
const rootTables = sg.tables.filter((t) => !groupedTableIds.has(t.id));
|
|
1891
|
+
const rootNotes = sg.notes.filter((n) => !groupedNoteIds.has(n.id));
|
|
1892
|
+
const rootCharts = sg.charts.filter((c) => !groupedChartIds.has(c.id));
|
|
1893
|
+
const rootMarkdowns = sg.markdowns.filter((m) => !groupedMarkdownIds.has(m.id));
|
|
1648
1894
|
// 3. Measure root groups bottom-up
|
|
1649
1895
|
for (const g of rootGroups)
|
|
1650
|
-
measure(g, nm, gm, tm, ntm, cm);
|
|
1896
|
+
measure(g, nm, gm, tm, ntm, cm, mdm);
|
|
1651
1897
|
// 4. Build root order
|
|
1652
1898
|
// sg.rootOrder preserves DSL declaration order.
|
|
1653
1899
|
// Fall back: groups, then nodes, then tables.
|
|
1654
1900
|
const rootOrder = sg.rootOrder?.length
|
|
1655
1901
|
? sg.rootOrder
|
|
1656
1902
|
: [
|
|
1657
|
-
...rootGroups.map(g => ({ kind:
|
|
1658
|
-
...rootNodes.map(n => ({ kind:
|
|
1659
|
-
...rootTables.map(t => ({ kind:
|
|
1660
|
-
...rootNotes.map(n => ({ kind:
|
|
1661
|
-
...rootCharts.map(c => ({ kind:
|
|
1903
|
+
...rootGroups.map((g) => ({ kind: "group", id: g.id })),
|
|
1904
|
+
...rootNodes.map((n) => ({ kind: "node", id: n.id })),
|
|
1905
|
+
...rootTables.map((t) => ({ kind: "table", id: t.id })),
|
|
1906
|
+
...rootNotes.map((n) => ({ kind: "note", id: n.id })),
|
|
1907
|
+
...rootCharts.map((c) => ({ kind: "chart", id: c.id })),
|
|
1908
|
+
...rootMarkdowns.map((m) => ({ kind: "markdown", id: m.id })),
|
|
1662
1909
|
];
|
|
1663
1910
|
// 5. Root-level layout
|
|
1664
1911
|
// sg.layout:
|
|
1665
1912
|
// 'row' → items flow left to right (default)
|
|
1666
1913
|
// 'column' → items flow top to bottom
|
|
1667
1914
|
// 'grid' → config columns=N grid
|
|
1668
|
-
const rootLayout = (sg.layout ??
|
|
1669
|
-
const rootCols = Number(sg.config[
|
|
1670
|
-
const useGrid = rootLayout ===
|
|
1671
|
-
const useColumn = rootLayout ===
|
|
1672
|
-
console.log('[layout] sized charts:', sg.charts.map(c => `${c.id} w=${c.w} h=${c.h}`));
|
|
1673
|
-
console.log('[layout] rootOrder chart refs:', rootOrder.filter(r => r.kind === 'chart'));
|
|
1915
|
+
const rootLayout = (sg.layout ?? "row");
|
|
1916
|
+
const rootCols = Number(sg.config["columns"] ?? 1);
|
|
1917
|
+
const useGrid = rootLayout === "grid" && rootCols > 0;
|
|
1918
|
+
const useColumn = rootLayout === "column";
|
|
1674
1919
|
if (useGrid) {
|
|
1675
1920
|
// ── Grid: per-row heights, per-column widths (no wasted space) ──
|
|
1676
1921
|
const cols = rootCols;
|
|
@@ -1681,22 +1926,26 @@ var AIDiagram = (function (exports) {
|
|
|
1681
1926
|
const col = idx % cols;
|
|
1682
1927
|
const row = Math.floor(idx / cols);
|
|
1683
1928
|
let w = 0, h = 0;
|
|
1684
|
-
if (ref.kind ===
|
|
1929
|
+
if (ref.kind === "group") {
|
|
1685
1930
|
w = gm.get(ref.id).w;
|
|
1686
1931
|
h = gm.get(ref.id).h;
|
|
1687
1932
|
}
|
|
1688
|
-
else if (ref.kind ===
|
|
1933
|
+
else if (ref.kind === "table") {
|
|
1689
1934
|
w = tm.get(ref.id).w;
|
|
1690
1935
|
h = tm.get(ref.id).h;
|
|
1691
1936
|
}
|
|
1692
|
-
else if (ref.kind ===
|
|
1937
|
+
else if (ref.kind === "note") {
|
|
1693
1938
|
w = ntm.get(ref.id).w;
|
|
1694
1939
|
h = ntm.get(ref.id).h;
|
|
1695
1940
|
}
|
|
1696
|
-
else if (ref.kind ===
|
|
1941
|
+
else if (ref.kind === "chart") {
|
|
1697
1942
|
w = cm.get(ref.id).w;
|
|
1698
1943
|
h = cm.get(ref.id).h;
|
|
1699
1944
|
}
|
|
1945
|
+
else if (ref.kind === "markdown") {
|
|
1946
|
+
w = mdm.get(ref.id).w;
|
|
1947
|
+
h = mdm.get(ref.id).h;
|
|
1948
|
+
}
|
|
1700
1949
|
else {
|
|
1701
1950
|
w = nm.get(ref.id).w;
|
|
1702
1951
|
h = nm.get(ref.id).h;
|
|
@@ -1719,22 +1968,26 @@ var AIDiagram = (function (exports) {
|
|
|
1719
1968
|
rootOrder.forEach((ref, idx) => {
|
|
1720
1969
|
const x = colX[idx % cols];
|
|
1721
1970
|
const y = rowY[Math.floor(idx / cols)];
|
|
1722
|
-
if (ref.kind ===
|
|
1971
|
+
if (ref.kind === "group") {
|
|
1723
1972
|
gm.get(ref.id).x = x;
|
|
1724
1973
|
gm.get(ref.id).y = y;
|
|
1725
1974
|
}
|
|
1726
|
-
else if (ref.kind ===
|
|
1975
|
+
else if (ref.kind === "table") {
|
|
1727
1976
|
tm.get(ref.id).x = x;
|
|
1728
1977
|
tm.get(ref.id).y = y;
|
|
1729
1978
|
}
|
|
1730
|
-
else if (ref.kind ===
|
|
1979
|
+
else if (ref.kind === "note") {
|
|
1731
1980
|
ntm.get(ref.id).x = x;
|
|
1732
1981
|
ntm.get(ref.id).y = y;
|
|
1733
1982
|
}
|
|
1734
|
-
else if (ref.kind ===
|
|
1983
|
+
else if (ref.kind === "chart") {
|
|
1735
1984
|
cm.get(ref.id).x = x;
|
|
1736
1985
|
cm.get(ref.id).y = y;
|
|
1737
1986
|
}
|
|
1987
|
+
else if (ref.kind === "markdown") {
|
|
1988
|
+
mdm.get(ref.id).x = x;
|
|
1989
|
+
mdm.get(ref.id).y = y;
|
|
1990
|
+
}
|
|
1738
1991
|
else {
|
|
1739
1992
|
nm.get(ref.id).x = x;
|
|
1740
1993
|
nm.get(ref.id).y = y;
|
|
@@ -1746,44 +1999,52 @@ var AIDiagram = (function (exports) {
|
|
|
1746
1999
|
let pos = MARGIN;
|
|
1747
2000
|
for (const ref of rootOrder) {
|
|
1748
2001
|
let w = 0, h = 0;
|
|
1749
|
-
if (ref.kind ===
|
|
2002
|
+
if (ref.kind === "group") {
|
|
1750
2003
|
w = gm.get(ref.id).w;
|
|
1751
2004
|
h = gm.get(ref.id).h;
|
|
1752
2005
|
}
|
|
1753
|
-
else if (ref.kind ===
|
|
2006
|
+
else if (ref.kind === "table") {
|
|
1754
2007
|
w = tm.get(ref.id).w;
|
|
1755
2008
|
h = tm.get(ref.id).h;
|
|
1756
2009
|
}
|
|
1757
|
-
else if (ref.kind ===
|
|
2010
|
+
else if (ref.kind === "note") {
|
|
1758
2011
|
w = ntm.get(ref.id).w;
|
|
1759
2012
|
h = ntm.get(ref.id).h;
|
|
1760
2013
|
}
|
|
1761
|
-
else if (ref.kind ===
|
|
2014
|
+
else if (ref.kind === "chart") {
|
|
1762
2015
|
w = cm.get(ref.id).w;
|
|
1763
2016
|
h = cm.get(ref.id).h;
|
|
1764
2017
|
}
|
|
2018
|
+
else if (ref.kind === "markdown") {
|
|
2019
|
+
w = mdm.get(ref.id).w;
|
|
2020
|
+
h = mdm.get(ref.id).h;
|
|
2021
|
+
}
|
|
1765
2022
|
else {
|
|
1766
2023
|
w = nm.get(ref.id).w;
|
|
1767
2024
|
h = nm.get(ref.id).h;
|
|
1768
2025
|
}
|
|
1769
2026
|
const x = useColumn ? MARGIN : pos;
|
|
1770
2027
|
const y = useColumn ? pos : MARGIN;
|
|
1771
|
-
if (ref.kind ===
|
|
2028
|
+
if (ref.kind === "group") {
|
|
1772
2029
|
gm.get(ref.id).x = x;
|
|
1773
2030
|
gm.get(ref.id).y = y;
|
|
1774
2031
|
}
|
|
1775
|
-
else if (ref.kind ===
|
|
2032
|
+
else if (ref.kind === "table") {
|
|
1776
2033
|
tm.get(ref.id).x = x;
|
|
1777
2034
|
tm.get(ref.id).y = y;
|
|
1778
2035
|
}
|
|
1779
|
-
else if (ref.kind ===
|
|
2036
|
+
else if (ref.kind === "note") {
|
|
1780
2037
|
ntm.get(ref.id).x = x;
|
|
1781
2038
|
ntm.get(ref.id).y = y;
|
|
1782
2039
|
}
|
|
1783
|
-
else if (ref.kind ===
|
|
2040
|
+
else if (ref.kind === "chart") {
|
|
1784
2041
|
cm.get(ref.id).x = x;
|
|
1785
2042
|
cm.get(ref.id).y = y;
|
|
1786
2043
|
}
|
|
2044
|
+
else if (ref.kind === "markdown") {
|
|
2045
|
+
mdm.get(ref.id).x = x;
|
|
2046
|
+
mdm.get(ref.id).y = y;
|
|
2047
|
+
}
|
|
1787
2048
|
else {
|
|
1788
2049
|
nm.get(ref.id).x = x;
|
|
1789
2050
|
nm.get(ref.id).y = y;
|
|
@@ -1793,10 +2054,9 @@ var AIDiagram = (function (exports) {
|
|
|
1793
2054
|
}
|
|
1794
2055
|
// 6. Place children within each root group (top-down, recursive)
|
|
1795
2056
|
for (const g of rootGroups)
|
|
1796
|
-
place(g, nm, gm, tm, ntm, cm);
|
|
2057
|
+
place(g, nm, gm, tm, ntm, cm, mdm);
|
|
1797
2058
|
// 7. Route edges and compute canvas size
|
|
1798
2059
|
routeEdges(sg);
|
|
1799
|
-
console.log('[layout] chart positions:', sg.charts.map(c => `${c.id} x=${c.x} y=${c.y}`));
|
|
1800
2060
|
computeBounds(sg, MARGIN);
|
|
1801
2061
|
return sg;
|
|
1802
2062
|
}
|
|
@@ -2525,6 +2785,26 @@ var AIDiagram = (function (exports) {
|
|
|
2525
2785
|
loadFont(raw);
|
|
2526
2786
|
return resolveFont(raw);
|
|
2527
2787
|
}
|
|
2788
|
+
function wrapText$1(text, maxWidth, fontSize) {
|
|
2789
|
+
const words = text.split(' ');
|
|
2790
|
+
const charsPerPx = fontSize * 0.55; // approximate
|
|
2791
|
+
const maxChars = Math.floor(maxWidth / charsPerPx);
|
|
2792
|
+
const lines = [];
|
|
2793
|
+
let current = '';
|
|
2794
|
+
for (const word of words) {
|
|
2795
|
+
const test = current ? `${current} ${word}` : word;
|
|
2796
|
+
if (test.length > maxChars && current) {
|
|
2797
|
+
lines.push(current);
|
|
2798
|
+
current = word;
|
|
2799
|
+
}
|
|
2800
|
+
else {
|
|
2801
|
+
current = test;
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
if (current)
|
|
2805
|
+
lines.push(current);
|
|
2806
|
+
return lines;
|
|
2807
|
+
}
|
|
2528
2808
|
// ── SVG text helpers ──────────────────────────────────────────────────────
|
|
2529
2809
|
/**
|
|
2530
2810
|
* Single-line SVG text element.
|
|
@@ -2867,7 +3147,9 @@ var AIDiagram = (function (exports) {
|
|
|
2867
3147
|
const gFontSize = Number(gs.fontSize ?? 12);
|
|
2868
3148
|
const gFont = resolveStyleFont$1(gs, diagramFont);
|
|
2869
3149
|
const gLetterSpacing = gs.letterSpacing;
|
|
2870
|
-
|
|
3150
|
+
if (g.label) {
|
|
3151
|
+
gg.appendChild(mkText(g.label, g.x + 14, g.y + 14, gFontSize, 500, gLabelColor, "start", gFont, gLetterSpacing));
|
|
3152
|
+
}
|
|
2871
3153
|
GL.appendChild(gg);
|
|
2872
3154
|
}
|
|
2873
3155
|
svg.appendChild(GL);
|
|
@@ -2960,7 +3242,9 @@ var AIDiagram = (function (exports) {
|
|
|
2960
3242
|
: textAlign === "right"
|
|
2961
3243
|
? n.x + n.w - 8
|
|
2962
3244
|
: n.x + n.w / 2;
|
|
2963
|
-
const lines = n.label.
|
|
3245
|
+
const lines = n.shape === 'text' && !n.label.includes('\n')
|
|
3246
|
+
? wrapText$1(n.label, n.w - 16, fontSize)
|
|
3247
|
+
: n.label.split('\n');
|
|
2964
3248
|
const verticalAlign = String(n.style?.verticalAlign ?? "middle");
|
|
2965
3249
|
const nodeBodyTop = n.y + 6;
|
|
2966
3250
|
const nodeBodyBottom = n.y + n.h - 6;
|
|
@@ -3157,6 +3441,54 @@ var AIDiagram = (function (exports) {
|
|
|
3157
3441
|
NoteL.appendChild(ng);
|
|
3158
3442
|
}
|
|
3159
3443
|
svg.appendChild(NoteL);
|
|
3444
|
+
markdownMap(sg);
|
|
3445
|
+
const MDL = mkGroup('markdown-layer');
|
|
3446
|
+
for (const m of sg.markdowns) {
|
|
3447
|
+
const mg = mkGroup(`markdown-${m.id}`, 'mdg');
|
|
3448
|
+
const mFont = resolveStyleFont$1(m.style, diagramFont);
|
|
3449
|
+
const baseColor = String(m.style?.color ?? palette.nodeText);
|
|
3450
|
+
const textAlign = String(m.style?.textAlign ?? 'left');
|
|
3451
|
+
const anchor = textAlign === 'right' ? 'end'
|
|
3452
|
+
: textAlign === 'center' ? 'middle'
|
|
3453
|
+
: 'start';
|
|
3454
|
+
const PAD = Number(m.style?.padding ?? 16);
|
|
3455
|
+
const textX = textAlign === 'right' ? m.x + m.w - PAD
|
|
3456
|
+
: textAlign === 'center' ? m.x + m.w / 2
|
|
3457
|
+
: m.x + PAD;
|
|
3458
|
+
let y = m.y + PAD;
|
|
3459
|
+
for (const line of m.lines) {
|
|
3460
|
+
if (line.kind === 'blank') {
|
|
3461
|
+
y += LINE_SPACING.blank;
|
|
3462
|
+
continue;
|
|
3463
|
+
}
|
|
3464
|
+
const fontSize = LINE_FONT_SIZE[line.kind];
|
|
3465
|
+
const fontWeight = LINE_FONT_WEIGHT[line.kind];
|
|
3466
|
+
const t = se('text');
|
|
3467
|
+
t.setAttribute('x', String(textX));
|
|
3468
|
+
t.setAttribute('y', String(y + fontSize / 2));
|
|
3469
|
+
t.setAttribute('text-anchor', anchor);
|
|
3470
|
+
t.setAttribute('dominant-baseline', 'middle');
|
|
3471
|
+
t.setAttribute('font-family', mFont);
|
|
3472
|
+
t.setAttribute('font-size', String(fontSize));
|
|
3473
|
+
t.setAttribute('font-weight', String(fontWeight));
|
|
3474
|
+
t.setAttribute('fill', baseColor);
|
|
3475
|
+
t.setAttribute('pointer-events', 'none');
|
|
3476
|
+
t.setAttribute('user-select', 'none');
|
|
3477
|
+
for (const run of line.runs) {
|
|
3478
|
+
const span = se('tspan');
|
|
3479
|
+
span.textContent = run.text;
|
|
3480
|
+
if (run.bold)
|
|
3481
|
+
span.setAttribute('font-weight', '700');
|
|
3482
|
+
if (run.italic)
|
|
3483
|
+
span.setAttribute('font-style', 'italic');
|
|
3484
|
+
t.appendChild(span);
|
|
3485
|
+
}
|
|
3486
|
+
mg.appendChild(t);
|
|
3487
|
+
y += LINE_SPACING[line.kind];
|
|
3488
|
+
}
|
|
3489
|
+
MDL.appendChild(mg);
|
|
3490
|
+
}
|
|
3491
|
+
svg.appendChild(MDL);
|
|
3160
3492
|
// ── Charts ────────────────────────────────────────────────
|
|
3161
3493
|
const CL = mkGroup("chart-layer");
|
|
3162
3494
|
for (const c of sg.charts) {
|
|
@@ -3439,10 +3771,6 @@ var AIDiagram = (function (exports) {
|
|
|
3439
3771
|
return resolveFont(raw);
|
|
3440
3772
|
}
|
|
3441
3773
|
// ── Canvas text helpers ────────────────────────────────────────────────────
|
|
3442
|
-
/**
|
|
3443
|
-
* Draw a single line of text.
|
|
3444
|
-
* align: 'left' | 'center' | 'right' (maps to ctx.textAlign)
|
|
3445
|
-
*/
|
|
3446
3774
|
function drawText(ctx, txt, x, y, sz = 14, wt = 500, col = '#1a1208', align = 'center', font = 'system-ui, sans-serif', letterSpacing) {
|
|
3447
3775
|
ctx.save();
|
|
3448
3776
|
ctx.font = `${wt} ${sz}px ${font}`;
|
|
@@ -3467,9 +3795,6 @@ var AIDiagram = (function (exports) {
|
|
|
3467
3795
|
}
|
|
3468
3796
|
ctx.restore();
|
|
3469
3797
|
}
|
|
3470
|
-
/**
|
|
3471
|
-
* Draw multiple lines of text, vertically centred around cy.
|
|
3472
|
-
*/
|
|
3473
3798
|
function drawMultilineText(ctx, lines, x, cy, sz = 14, wt = 500, col = '#1a1208', align = 'center', lineH = 18, font = 'system-ui, sans-serif', letterSpacing) {
|
|
3474
3799
|
const totalH = (lines.length - 1) * lineH;
|
|
3475
3800
|
const startY = cy - totalH / 2;
|
|
@@ -3477,6 +3802,27 @@ var AIDiagram = (function (exports) {
|
|
|
3477
3802
|
drawText(ctx, line, x, startY + i * lineH, sz, wt, col, align, font, letterSpacing);
|
|
3478
3803
|
});
|
|
3479
3804
|
}
|
|
3805
|
+
// Soft word-wrap for `text` shape nodes
|
|
3806
|
+
function wrapText(text, maxWidth, fontSize) {
|
|
3807
|
+
const charWidth = fontSize * 0.55;
|
|
3808
|
+
const maxChars = Math.floor(maxWidth / charWidth);
|
|
3809
|
+
const words = text.split(' ');
|
|
3810
|
+
const lines = [];
|
|
3811
|
+
let current = '';
|
|
3812
|
+
for (const word of words) {
|
|
3813
|
+
const test = current ? `${current} ${word}` : word;
|
|
3814
|
+
if (test.length > maxChars && current) {
|
|
3815
|
+
lines.push(current);
|
|
3816
|
+
current = word;
|
|
3817
|
+
}
|
|
3818
|
+
else {
|
|
3819
|
+
current = test;
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
if (current)
|
|
3823
|
+
lines.push(current);
|
|
3824
|
+
return lines.length ? lines : [text];
|
|
3825
|
+
}
|
|
3480
3826
|
// ── Arrow direction ────────────────────────────────────────────────────────
|
|
3481
3827
|
function connMeta(connector) {
|
|
3482
3828
|
if (connector === '--')
|
|
@@ -3568,7 +3914,7 @@ var AIDiagram = (function (exports) {
|
|
|
3568
3914
|
], opts);
|
|
3569
3915
|
break;
|
|
3570
3916
|
case 'text':
|
|
3571
|
-
break;
|
|
3917
|
+
break; // no shape drawn
|
|
3572
3918
|
case 'image': {
|
|
3573
3919
|
if (n.imageUrl) {
|
|
3574
3920
|
const img = new Image();
|
|
@@ -3658,7 +4004,9 @@ var AIDiagram = (function (exports) {
|
|
|
3658
4004
|
// ── Title ────────────────────────────────────────────────
|
|
3659
4005
|
if (sg.title) {
|
|
3660
4006
|
const titleSize = Number(sg.config['title-size'] ?? 18);
|
|
3661
|
-
|
|
4007
|
+
const titleWeight = Number(sg.config['title-weight'] ?? 600);
|
|
4008
|
+
const titleColor = String(sg.config['title-color'] ?? palette.titleText);
|
|
4009
|
+
drawText(ctx, sg.title, sg.width / 2, 28, titleSize, titleWeight, titleColor, 'center', diagramFont);
|
|
3662
4010
|
}
|
|
3663
4011
|
// ── Groups (outermost first) ─────────────────────────────
|
|
3664
4012
|
const sortedGroups = [...sg.groups].sort((a, b) => groupDepth(a, gm) - groupDepth(b, gm));
|
|
@@ -3674,13 +4022,16 @@ var AIDiagram = (function (exports) {
|
|
|
3674
4022
|
strokeWidth: Number(gs.strokeWidth ?? 1.2),
|
|
3675
4023
|
strokeLineDash: gs.strokeDash ?? palette.groupDash,
|
|
3676
4024
|
});
|
|
3677
|
-
// ── Group label
|
|
3678
|
-
//
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
4025
|
+
// ── Group label ──────────────────────────────────────
|
|
4026
|
+
// Only render when label has content — empty label = no reserved space
|
|
4027
|
+
// supports: font, font-size, letter-spacing (always left-anchored)
|
|
4028
|
+
if (g.label) {
|
|
4029
|
+
const gFontSize = Number(gs.fontSize ?? 12);
|
|
4030
|
+
const gFont = resolveStyleFont(gs, diagramFont);
|
|
4031
|
+
const gLetterSpacing = gs.letterSpacing;
|
|
4032
|
+
const gLabelColor = gs.color ? String(gs.color) : palette.groupLabel;
|
|
4033
|
+
drawText(ctx, g.label, g.x + 14, g.y + 16, gFontSize, 500, gLabelColor, 'left', gFont, gLetterSpacing);
|
|
4034
|
+
}
|
|
3684
4035
|
}
|
|
3685
4036
|
// ── Edges ─────────────────────────────────────────────────
|
|
3686
4037
|
for (const e of sg.edges) {
|
|
@@ -3733,7 +4084,8 @@ var AIDiagram = (function (exports) {
|
|
|
3733
4084
|
for (const n of sg.nodes) {
|
|
3734
4085
|
renderShape(rc, ctx, n, palette, R);
|
|
3735
4086
|
// ── Node / text typography ─────────────────────────
|
|
3736
|
-
// supports: font, font-size, letter-spacing, text-align,
|
|
4087
|
+
// supports: font, font-size, letter-spacing, text-align,
|
|
4088
|
+
// vertical-align, line-height, word-wrap (text shape)
|
|
3737
4089
|
const fontSize = Number(n.style?.fontSize ?? (n.shape === 'text' ? 13 : 14));
|
|
3738
4090
|
const fontWeight = n.style?.fontWeight ?? (n.shape === 'text' ? 400 : 500);
|
|
3739
4091
|
const textColor = String(n.style?.color ??
|
|
@@ -3742,15 +4094,28 @@ var AIDiagram = (function (exports) {
|
|
|
3742
4094
|
const textAlign = String(n.style?.textAlign ?? 'center');
|
|
3743
4095
|
const lineHeight = Number(n.style?.lineHeight ?? 1.3) * fontSize;
|
|
3744
4096
|
const letterSpacing = n.style?.letterSpacing;
|
|
4097
|
+
const vertAlign = String(n.style?.verticalAlign ?? 'middle');
|
|
4098
|
+
// x shifts for left/right alignment
|
|
3745
4099
|
const textX = textAlign === 'left' ? n.x + 8
|
|
3746
4100
|
: textAlign === 'right' ? n.x + n.w - 8
|
|
3747
4101
|
: n.x + n.w / 2;
|
|
3748
|
-
|
|
4102
|
+
// word-wrap for text shape; explicit \n for all others
|
|
4103
|
+
const rawLines = n.label.split('\n');
|
|
4104
|
+
const lines = n.shape === 'text' && rawLines.length === 1
|
|
4105
|
+
? wrapText(n.label, n.w - 16, fontSize)
|
|
4106
|
+
: rawLines;
|
|
4107
|
+
// vertical-align: compute textCY from top/middle/bottom
|
|
4108
|
+
const nodeBodyTop = n.y + 6;
|
|
4109
|
+
const nodeBodyBottom = n.y + n.h - 6;
|
|
4110
|
+
const blockH = (lines.length - 1) * lineHeight;
|
|
4111
|
+
const textCY = vertAlign === 'top' ? nodeBodyTop + blockH / 2
|
|
4112
|
+
: vertAlign === 'bottom' ? nodeBodyBottom - blockH / 2
|
|
4113
|
+
: n.y + n.h / 2; // middle (default)
|
|
3749
4114
|
if (lines.length > 1) {
|
|
3750
|
-
drawMultilineText(ctx, lines, textX,
|
|
4115
|
+
drawMultilineText(ctx, lines, textX, textCY, fontSize, fontWeight, textColor, textAlign, lineHeight, nodeFont, letterSpacing);
|
|
3751
4116
|
}
|
|
3752
4117
|
else {
|
|
3753
|
-
drawText(ctx,
|
|
4118
|
+
drawText(ctx, lines[0] ?? '', textX, textCY, fontSize, fontWeight, textColor, textAlign, nodeFont, letterSpacing);
|
|
3754
4119
|
}
|
|
3755
4120
|
}
|
|
3756
4121
|
// ── Tables ────────────────────────────────────────────────
|
|
@@ -3761,7 +4126,8 @@ var AIDiagram = (function (exports) {
|
|
|
3761
4126
|
const textCol = String(gs.color ?? palette.tableText);
|
|
3762
4127
|
const pad = t.labelH;
|
|
3763
4128
|
// ── Table-level font ────────────────────────────────
|
|
3764
|
-
// supports: font, font-size, letter-spacing
|
|
4129
|
+
// supports: font, font-size, letter-spacing
|
|
4130
|
+
// cells also support text-align
|
|
3765
4131
|
const tFontSize = Number(gs.fontSize ?? 12);
|
|
3766
4132
|
const tFont = resolveStyleFont(gs, diagramFont);
|
|
3767
4133
|
const tLetterSpacing = gs.letterSpacing;
|
|
@@ -3772,8 +4138,7 @@ var AIDiagram = (function (exports) {
|
|
|
3772
4138
|
rc.line(t.x, t.y + pad, t.x + t.w, t.y + pad, {
|
|
3773
4139
|
roughness: 0.6, seed: hashStr$1(t.id + 'l'), stroke: strk, strokeWidth: 1,
|
|
3774
4140
|
});
|
|
3775
|
-
// ── Table label:
|
|
3776
|
-
// always left-anchored
|
|
4141
|
+
// ── Table label: always left-anchored ───────────────
|
|
3777
4142
|
drawText(ctx, t.label, t.x + 10, t.y + pad / 2, tFontSize, 500, textCol, 'left', tFont, tLetterSpacing);
|
|
3778
4143
|
let rowY = t.y + pad;
|
|
3779
4144
|
for (const row of t.rows) {
|
|
@@ -3787,7 +4152,7 @@ var AIDiagram = (function (exports) {
|
|
|
3787
4152
|
stroke: row.kind === 'header' ? strk : palette.tableDivider,
|
|
3788
4153
|
strokeWidth: row.kind === 'header' ? 1.2 : 0.6,
|
|
3789
4154
|
});
|
|
3790
|
-
// ── Cell text
|
|
4155
|
+
// ── Cell text ───────────────────────────────────
|
|
3791
4156
|
// header always centered; data rows respect gs.textAlign
|
|
3792
4157
|
const cellAlignProp = (row.kind === 'header'
|
|
3793
4158
|
? 'center'
|
|
@@ -3835,22 +4200,91 @@ var AIDiagram = (function (exports) {
|
|
|
3835
4200
|
], { roughness: 0.4, seed: hashStr$1(n.id + 'f'),
|
|
3836
4201
|
fill: palette.noteFold, fillStyle: 'solid', stroke: strk, strokeWidth: 0.8 });
|
|
3837
4202
|
// ── Note typography ─────────────────────────────────
|
|
3838
|
-
// supports: font, font-size, letter-spacing, text-align,
|
|
4203
|
+
// supports: font, font-size, letter-spacing, text-align,
|
|
4204
|
+
// vertical-align, line-height
|
|
3839
4205
|
const nFontSize = Number(gs.fontSize ?? 12);
|
|
3840
4206
|
const nFont = resolveStyleFont(gs, diagramFont);
|
|
3841
4207
|
const nLetterSpacing = gs.letterSpacing;
|
|
3842
4208
|
const nLineHeight = Number(gs.lineHeight ?? 1.4) * nFontSize;
|
|
3843
4209
|
const nTextAlign = String(gs.textAlign ?? 'left');
|
|
4210
|
+
const nVertAlign = String(gs.verticalAlign ?? 'top');
|
|
3844
4211
|
const nColor = String(gs.color ?? palette.noteText);
|
|
3845
4212
|
const nTextX = nTextAlign === 'right' ? x + w - fold - 6
|
|
3846
4213
|
: nTextAlign === 'center' ? x + (w - fold) / 2
|
|
3847
4214
|
: x + 12;
|
|
4215
|
+
// vertical-align inside note body (below fold)
|
|
4216
|
+
const bodyTop = y + fold + 8;
|
|
4217
|
+
const bodyBottom = y + h - 8;
|
|
4218
|
+
const blockH = (n.lines.length - 1) * nLineHeight;
|
|
4219
|
+
const blockCY = nVertAlign === 'bottom' ? bodyBottom - blockH / 2
|
|
4220
|
+
: nVertAlign === 'middle' ? (bodyTop + bodyBottom) / 2
|
|
4221
|
+
: bodyTop + blockH / 2; // top (default)
|
|
3848
4222
|
if (n.lines.length > 1) {
|
|
3849
|
-
const blockCY = y + fold / 2 + (h - fold) / 2;
|
|
3850
4223
|
drawMultilineText(ctx, n.lines, nTextX, blockCY, nFontSize, 400, nColor, nTextAlign, nLineHeight, nFont, nLetterSpacing);
|
|
3851
4224
|
}
|
|
3852
4225
|
else {
|
|
3853
|
-
drawText(ctx, n.lines[0] ?? '', nTextX,
|
|
4226
|
+
drawText(ctx, n.lines[0] ?? '', nTextX, blockCY, nFontSize, 400, nColor, nTextAlign, nFont, nLetterSpacing);
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
// ── Markdown blocks ────────────────────────────────────────
|
|
4230
|
+
// Renders prose with Markdown headings and bold/italic inline spans.
|
|
4231
|
+
// Canvas has no native bold-within-a-run, so each run is drawn
|
|
4232
|
+
// individually with its own ctx.font setting.
|
|
4233
|
+
for (const m of (sg.markdowns ?? [])) {
|
|
4234
|
+
const mFont = resolveStyleFont(m.style, diagramFont);
|
|
4235
|
+
const baseColor = String(m.style?.color ?? palette.nodeText);
|
|
4236
|
+
const textAlign = String(m.style?.textAlign ?? 'left');
|
|
4237
|
+
const PAD = Number(m.style?.padding ?? 16);
|
|
4238
|
+
const anchorX = textAlign === 'right' ? m.x + m.w - PAD
|
|
4239
|
+
: textAlign === 'center' ? m.x + m.w / 2
|
|
4240
|
+
: m.x + PAD;
|
|
4241
|
+
let y = m.y + PAD;
|
|
4242
|
+
for (const line of m.lines) {
|
|
4243
|
+
if (line.kind === 'blank') {
|
|
4244
|
+
y += LINE_SPACING.blank;
|
|
4245
|
+
continue;
|
|
4246
|
+
}
|
|
4247
|
+
const fontSize = LINE_FONT_SIZE[line.kind];
|
|
4248
|
+
const fontWeight = LINE_FONT_WEIGHT[line.kind];
|
|
4249
|
+
const lineY = y + fontSize / 2;
|
|
4250
|
+
// Measure total run width for left-offset when runs mix bold/italic
|
|
4251
|
+
// Simple: draw each run consecutively from a computed start x
|
|
4252
|
+
ctx.save();
|
|
4253
|
+
ctx.textBaseline = 'middle';
|
|
4254
|
+
ctx.fillStyle = baseColor;
|
|
4255
|
+
if (textAlign === 'center' || textAlign === 'right') {
|
|
4256
|
+
// Measure full line width first
|
|
4257
|
+
let totalW = 0;
|
|
4258
|
+
for (const run of line.runs) {
|
|
4259
|
+
const runStyle = run.italic ? 'italic ' : '';
|
|
4260
|
+
const runWeight = run.bold ? 700 : fontWeight;
|
|
4261
|
+
ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
|
|
4262
|
+
totalW += ctx.measureText(run.text).width;
|
|
4263
|
+
}
|
|
4264
|
+
let runX = textAlign === 'center' ? anchorX - totalW / 2 : anchorX - totalW;
|
|
4265
|
+
ctx.textAlign = 'left';
|
|
4266
|
+
for (const run of line.runs) {
|
|
4267
|
+
const runStyle = run.italic ? 'italic ' : '';
|
|
4268
|
+
const runWeight = run.bold ? 700 : fontWeight;
|
|
4269
|
+
ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
|
|
4270
|
+
ctx.fillText(run.text, runX, lineY);
|
|
4271
|
+
runX += ctx.measureText(run.text).width;
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
else {
|
|
4275
|
+
// left-aligned — draw runs left to right from anchorX
|
|
4276
|
+
let runX = anchorX;
|
|
4277
|
+
ctx.textAlign = 'left';
|
|
4278
|
+
for (const run of line.runs) {
|
|
4279
|
+
const runStyle = run.italic ? 'italic ' : '';
|
|
4280
|
+
const runWeight = run.bold ? 700 : fontWeight;
|
|
4281
|
+
ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
|
|
4282
|
+
ctx.fillText(run.text, runX, lineY);
|
|
4283
|
+
runX += ctx.measureText(run.text).width;
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
ctx.restore();
|
|
4287
|
+
y += LINE_SPACING[line.kind];
|
|
3854
4288
|
}
|
|
3855
4289
|
}
|
|
3856
4290
|
// ── Charts ────────────────────────────────────────────────
|
|
@@ -4810,6 +5244,89 @@ var AIDiagram = (function (exports) {
|
|
|
4810
5244
|
}
|
|
4811
5245
|
}
|
|
4812
5246
|
|
|
5247
|
+
// ============================================================
|
|
5248
|
+
// sketchmark — Encrypted sharing
|
|
5249
|
+
// Diagram DSL is encrypted in the browser.
|
|
5250
|
+
// The server stores an opaque blob it cannot read.
|
|
5251
|
+
// The decryption key lives only in the URL fragment (#key=...).
|
|
5252
|
+
// ============================================================
|
|
5253
|
+
const WORKER_URL = 'https://sketchmark.anmism.workers.dev';
|
|
5254
|
+
// ── Crypto helpers ────────────────────────────────────────
|
|
5255
|
+
async function generateKey() {
|
|
5256
|
+
return crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, // extractable so we can export to URL
|
|
5257
|
+
['encrypt', 'decrypt']);
|
|
5258
|
+
}
|
|
5259
|
+
async function keyToBase64(key) {
|
|
5260
|
+
const raw = await crypto.subtle.exportKey('raw', key);
|
|
5261
|
+
return btoa(String.fromCharCode(...new Uint8Array(raw)));
|
|
5262
|
+
}
|
|
5263
|
+
async function base64ToKey(b64) {
|
|
5264
|
+
const raw = Uint8Array.from(atob(b64), c => c.charCodeAt(0));
|
|
5265
|
+
return crypto.subtle.importKey('raw', raw, { name: 'AES-GCM' }, false, // not extractable on the receiving end
|
|
5266
|
+
['decrypt']);
|
|
5267
|
+
}
|
|
5268
|
+
// ── Encrypt ───────────────────────────────────────────────
|
|
5269
|
+
async function encryptDSL(dsl, key) {
|
|
5270
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
5271
|
+
const encoded = new TextEncoder().encode(dsl);
|
|
5272
|
+
const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded);
|
|
5273
|
+
// prepend iv to the blob: [ iv (12 bytes) | ciphertext ]
|
|
5274
|
+
const result = new Uint8Array(12 + encrypted.byteLength);
|
|
5275
|
+
result.set(iv, 0);
|
|
5276
|
+
result.set(new Uint8Array(encrypted), 12);
|
|
5277
|
+
return result;
|
|
5278
|
+
}
|
|
5279
|
+
// ── Decrypt ───────────────────────────────────────────────
|
|
5280
|
+
async function decryptBlob(blob, key) {
|
|
5281
|
+
const iv = blob.slice(0, 12);
|
|
5282
|
+
const ciphertext = blob.slice(12);
|
|
5283
|
+
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
|
|
5284
|
+
return new TextDecoder().decode(decrypted);
|
|
5285
|
+
}
|
|
5286
|
+
// ── Public API ────────────────────────────────────────────
|
|
5287
|
+
/**
|
|
5288
|
+
* Encrypt DSL, upload to worker, return shareable URL.
|
|
5289
|
+
* The URL fragment (#key=...) never reaches the server.
|
|
5290
|
+
*/
|
|
5291
|
+
async function shareDiagram(dsl) {
|
|
5292
|
+
const key = await generateKey();
|
|
5293
|
+
const blob = await encryptDSL(dsl, key);
|
|
5294
|
+
const keyB64 = await keyToBase64(key);
|
|
5295
|
+
const res = await fetch(`${WORKER_URL}/api/blob`, {
|
|
5296
|
+
method: 'POST',
|
|
5297
|
+
headers: { 'Content-Type': 'application/octet-stream' },
|
|
5298
|
+
body: blob.buffer,
|
|
5299
|
+
});
|
|
5300
|
+
if (!res.ok)
|
|
5301
|
+
throw new Error(`Upload failed: ${res.status}`);
|
|
5302
|
+
const { id } = await res.json();
|
|
5303
|
+
// key goes into the fragment — browser never sends this to any server
|
|
5304
|
+
return `${window.location.origin}/playground.html?s=${id}#key=${keyB64}`;
|
|
5305
|
+
}
|
|
5306
|
+
/**
|
|
5307
|
+
* Read ?s= and #key= from the current URL, fetch + decrypt the diagram.
|
|
5308
|
+
* Returns null if no share params found.
|
|
5309
|
+
*/
|
|
5310
|
+
async function loadSharedDiagram() {
|
|
5311
|
+
const params = new URLSearchParams(window.location.search);
|
|
5312
|
+
const id = params.get('s');
|
|
5313
|
+
if (!id)
|
|
5314
|
+
return null;
|
|
5315
|
+
// key is in the fragment — parse manually, not via URLSearchParams
|
|
5316
|
+
// (URLSearchParams on hash strips the #)
|
|
5317
|
+
const fragment = window.location.hash.slice(1);
|
|
5318
|
+
const keyMatch = fragment.match(/key=([^&]+)/);
|
|
5319
|
+
if (!keyMatch)
|
|
5320
|
+
return null;
|
|
5321
|
+
const keyB64 = keyMatch[1];
|
|
5322
|
+
const res = await fetch(`${WORKER_URL}/api/blob/${id}`);
|
|
5323
|
+
if (!res.ok)
|
|
5324
|
+
throw new Error('Diagram not found or expired');
|
|
5325
|
+
const blob = await res.arrayBuffer();
|
|
5326
|
+
const key = await base64ToKey(keyB64);
|
|
5327
|
+
return decryptBlob(blob, key);
|
|
5328
|
+
}
|
|
5329
|
+
|
|
4813
5330
|
// ============================================================
|
|
4814
5331
|
// sketchmark — Public API
|
|
4815
5332
|
// ============================================================
|
|
@@ -4901,6 +5418,8 @@ var AIDiagram = (function (exports) {
|
|
|
4901
5418
|
exports.lerp = lerp;
|
|
4902
5419
|
exports.listThemes = listThemes;
|
|
4903
5420
|
exports.loadFont = loadFont;
|
|
5421
|
+
exports.loadSharedDiagram = loadSharedDiagram;
|
|
5422
|
+
exports.markdownMap = markdownMap;
|
|
4904
5423
|
exports.nodeMap = nodeMap;
|
|
4905
5424
|
exports.parse = parse;
|
|
4906
5425
|
exports.parseHex = parseHex;
|
|
@@ -4910,6 +5429,7 @@ var AIDiagram = (function (exports) {
|
|
|
4910
5429
|
exports.renderToSVG = renderToSVG;
|
|
4911
5430
|
exports.resolveFont = resolveFont;
|
|
4912
5431
|
exports.resolvePalette = resolvePalette;
|
|
5432
|
+
exports.shareDiagram = shareDiagram;
|
|
4913
5433
|
exports.sleep = sleep;
|
|
4914
5434
|
exports.svgToPNGDataURL = svgToPNGDataURL;
|
|
4915
5435
|
exports.svgToString = svgToString;
|