mermaid2term 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1288 @@
1
+ 'use strict';
2
+
3
+ // src/core/types.ts
4
+ var DEFAULT_RENDER_OPTIONS = {
5
+ charset: "unicode",
6
+ maxWidth: 0,
7
+ paddingX: 2,
8
+ paddingY: 1,
9
+ borderPadding: 1,
10
+ showCoords: false
11
+ };
12
+
13
+ // src/diagrams/flowchart/parser.ts
14
+ var DIRECTION_PATTERN = /^(?:graph|flowchart)\s+(LR|RL|TD|TB|BT)\s*$/i;
15
+ var NODE_DEF_PATTERNS = [
16
+ { pattern: /^(\w+)\[\[(.+)\]\]$/, shape: "subroutine" },
17
+ { pattern: /^(\w+)\[\((.+)\)\]$/, shape: "cylinder" },
18
+ { pattern: /^(\w+)\(\((.+)\)\)$/, shape: "circle" },
19
+ { pattern: /^(\w+)\{\{(.+)\}\}$/, shape: "hexagon" },
20
+ { pattern: /^(\w+)\{(.+)\}$/, shape: "diamond" },
21
+ { pattern: /^(\w+)\((.+)\)$/, shape: "round" },
22
+ { pattern: /^(\w+)\[\/(.+)\/\]$/, shape: "parallelogram" },
23
+ { pattern: /^(\w+)\[\\(.+)\\\]$/, shape: "parallelogram" },
24
+ { pattern: /^(\w+)>(.+)\]$/, shape: "asymmetric" },
25
+ { pattern: /^(\w+)\[(.+)\]$/, shape: "rect" },
26
+ { pattern: /^(\w+)$/, shape: "rect" }
27
+ ];
28
+ var EDGE_PATTERNS = [
29
+ { pattern: /^-->\|(.+?)\|$/, style: "solid", arrowType: "arrow", hasLabel: true },
30
+ { pattern: /^-->$/, style: "solid", arrowType: "arrow", hasLabel: false },
31
+ { pattern: /^---\|(.+?)\|$/, style: "solid", arrowType: "open", hasLabel: true },
32
+ { pattern: /^---$/, style: "solid", arrowType: "open", hasLabel: false },
33
+ { pattern: /^-\.->\|(.+?)\|$/, style: "dotted", arrowType: "arrow", hasLabel: true },
34
+ { pattern: /^-\.->$/, style: "dotted", arrowType: "arrow", hasLabel: false },
35
+ { pattern: /^-\.-\|(.+?)\|$/, style: "dotted", arrowType: "open", hasLabel: true },
36
+ { pattern: /^-\.-$/, style: "dotted", arrowType: "open", hasLabel: false },
37
+ { pattern: /^==>\|(.+?)\|$/, style: "thick", arrowType: "arrow", hasLabel: true },
38
+ { pattern: /^==>$/, style: "thick", arrowType: "arrow", hasLabel: false },
39
+ { pattern: /^--o$/, style: "solid", arrowType: "circle", hasLabel: false },
40
+ { pattern: /^--x$/, style: "solid", arrowType: "cross", hasLabel: false },
41
+ { pattern: /^<-->$/, style: "solid", arrowType: "arrow", hasLabel: false, bidirectional: true }
42
+ ];
43
+ var CLASSDEF_PATTERN = /^classDef\s+(\w+)\s+(.+)$/;
44
+ var CLASS_ASSIGNMENT_PATTERN = /^class\s+(.+)\s+(\w+)$/;
45
+ var NODE_CLASS_PATTERN = /^(\w+):::(\w+)$/;
46
+ var AMPERSAND_SPLIT = /\s*&\s*/;
47
+ function parseNodeDefinition(text) {
48
+ const trimmed = text.trim();
49
+ const classMatch = trimmed.match(NODE_CLASS_PATTERN);
50
+ let nodeText = trimmed;
51
+ let styleClass;
52
+ if (classMatch) {
53
+ nodeText = classMatch[1];
54
+ styleClass = classMatch[2];
55
+ }
56
+ for (const { pattern, shape } of NODE_DEF_PATTERNS) {
57
+ const match = nodeText.match(pattern);
58
+ if (match) {
59
+ const id = match[1];
60
+ const label = match[2] ?? match[1];
61
+ return { id, label, shape, styleClass };
62
+ }
63
+ }
64
+ return { id: trimmed, label: trimmed, shape: "rect", styleClass };
65
+ }
66
+ function parseEdgeOperator(text) {
67
+ for (const edgeDef of EDGE_PATTERNS) {
68
+ const match = text.match(edgeDef.pattern);
69
+ if (match) {
70
+ return {
71
+ style: edgeDef.style,
72
+ arrowType: edgeDef.arrowType,
73
+ label: edgeDef.hasLabel ? match[1] : void 0,
74
+ bidirectional: edgeDef.bidirectional ?? false
75
+ };
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+ function parseStyleClass(propsString) {
81
+ const props = {};
82
+ const pairs = propsString.split(",");
83
+ for (const pair of pairs) {
84
+ const [key, value] = pair.split(":").map((s) => s.trim());
85
+ if (key && value) {
86
+ switch (key) {
87
+ case "fill":
88
+ props.fill = value;
89
+ break;
90
+ case "stroke":
91
+ props.stroke = value;
92
+ break;
93
+ case "color":
94
+ props.color = value;
95
+ break;
96
+ case "stroke-width":
97
+ props.strokeWidth = value;
98
+ break;
99
+ }
100
+ }
101
+ }
102
+ return props;
103
+ }
104
+ var EDGE_OPERATOR_REGEX = /(<-->|-->\|[^|]+\||-->|---\|[^|]+\||---|-\.->\|[^|]+\||-\.->|-\.-\|[^|]+\||-\.-|==>\|[^|]+\||==>|--o|--x)/;
105
+ function tokenizeEdgeLine(line) {
106
+ const tokens = [];
107
+ let current = "";
108
+ let inBracket = 0;
109
+ let inParen = 0;
110
+ let inBrace = 0;
111
+ let inPipe = false;
112
+ for (let i = 0; i < line.length; i++) {
113
+ const char = line[i];
114
+ if (char === "[") inBracket++;
115
+ else if (char === "]") inBracket--;
116
+ else if (char === "(") inParen++;
117
+ else if (char === ")") inParen--;
118
+ else if (char === "{") inBrace++;
119
+ else if (char === "}") inBrace--;
120
+ else if (char === "|") inPipe = !inPipe;
121
+ const inContainer = inBracket > 0 || inParen > 0 || inBrace > 0 || inPipe;
122
+ if (!inContainer && char === " ") {
123
+ if (current.trim()) {
124
+ tokens.push(current.trim());
125
+ }
126
+ current = "";
127
+ } else {
128
+ current += char;
129
+ }
130
+ }
131
+ if (current.trim()) {
132
+ tokens.push(current.trim());
133
+ }
134
+ const expandedTokens = [];
135
+ for (const token of tokens) {
136
+ const parts = token.split(EDGE_OPERATOR_REGEX).filter((p) => p);
137
+ if (parts.length > 1) {
138
+ expandedTokens.push(...parts);
139
+ } else {
140
+ expandedTokens.push(token);
141
+ }
142
+ }
143
+ return expandedTokens;
144
+ }
145
+ function parseFlowchart(input) {
146
+ const trimmedInput = input.trim();
147
+ const normalizedInput = trimmedInput.includes(";") ? trimmedInput.replace(/;/g, "\n") : trimmedInput;
148
+ const lines = normalizedInput.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("%%"));
149
+ if (lines.length === 0) {
150
+ throw new Error("Empty flowchart input");
151
+ }
152
+ const directionMatch = lines[0].match(DIRECTION_PATTERN);
153
+ if (!directionMatch) {
154
+ throw new Error(`Invalid flowchart header. Expected "graph LR|RL|TD|TB|BT" or "flowchart LR|RL|TD|TB|BT", got: "${lines[0]}"`);
155
+ }
156
+ let direction = directionMatch[1].toUpperCase();
157
+ if (direction === "TB") direction = "TD";
158
+ const graphDirection = direction;
159
+ const nodes = /* @__PURE__ */ new Map();
160
+ const edges = [];
161
+ const styleClasses = /* @__PURE__ */ new Map();
162
+ const subgraphs = [];
163
+ const nodeClassAssignments = /* @__PURE__ */ new Map();
164
+ let nodeIndex = 0;
165
+ let edgeIndex = 0;
166
+ function getOrCreateNode(parsed) {
167
+ let node = nodes.get(parsed.id);
168
+ if (!node) {
169
+ node = {
170
+ id: parsed.id,
171
+ label: parsed.label,
172
+ shape: parsed.shape,
173
+ styleClass: parsed.styleClass,
174
+ index: nodeIndex++
175
+ };
176
+ nodes.set(parsed.id, node);
177
+ } else {
178
+ if (parsed.label !== parsed.id) {
179
+ node.label = parsed.label;
180
+ }
181
+ if (parsed.shape !== "rect") {
182
+ node.shape = parsed.shape;
183
+ }
184
+ if (parsed.styleClass) {
185
+ node.styleClass = parsed.styleClass;
186
+ }
187
+ }
188
+ return node;
189
+ }
190
+ for (let i = 1; i < lines.length; i++) {
191
+ const line = lines[i];
192
+ const classDefMatch = line.match(CLASSDEF_PATTERN);
193
+ if (classDefMatch) {
194
+ const [, name, propsString] = classDefMatch;
195
+ const props = parseStyleClass(propsString);
196
+ styleClasses.set(name, { name, ...props });
197
+ continue;
198
+ }
199
+ const classAssignMatch = line.match(CLASS_ASSIGNMENT_PATTERN);
200
+ if (classAssignMatch) {
201
+ const [, nodeIds, className] = classAssignMatch;
202
+ for (const nodeId of nodeIds.split(",").map((s) => s.trim())) {
203
+ nodeClassAssignments.set(nodeId, className);
204
+ }
205
+ continue;
206
+ }
207
+ if (line.startsWith("subgraph") || line === "end") {
208
+ continue;
209
+ }
210
+ const tokens = tokenizeEdgeLine(line);
211
+ if (tokens.length === 0) continue;
212
+ if (tokens.length === 1) {
213
+ const ampersandParts = tokens[0].split(AMPERSAND_SPLIT);
214
+ for (const part of ampersandParts) {
215
+ const parsed = parseNodeDefinition(part);
216
+ getOrCreateNode(parsed);
217
+ }
218
+ continue;
219
+ }
220
+ let currentFromNodes = [];
221
+ let expectingEdge = false;
222
+ for (const token of tokens) {
223
+ const edgeOp = parseEdgeOperator(token);
224
+ if (edgeOp) {
225
+ expectingEdge = true;
226
+ continue;
227
+ }
228
+ const ampersandParts = token.split(AMPERSAND_SPLIT);
229
+ const parsedNodes = ampersandParts.map(parseNodeDefinition);
230
+ if (currentFromNodes.length > 0 && expectingEdge) {
231
+ const lastEdgeOp = (() => {
232
+ for (let j = tokens.indexOf(token) - 1; j >= 0; j--) {
233
+ const op = parseEdgeOperator(tokens[j]);
234
+ if (op) return op;
235
+ }
236
+ return { style: "solid", arrowType: "arrow", label: void 0, bidirectional: false };
237
+ })();
238
+ for (const fromParsed of currentFromNodes) {
239
+ const fromNode = getOrCreateNode(fromParsed);
240
+ for (const toParsed of parsedNodes) {
241
+ const toNode = getOrCreateNode(toParsed);
242
+ edges.push({
243
+ from: fromNode.id,
244
+ to: toNode.id,
245
+ label: lastEdgeOp.label,
246
+ style: lastEdgeOp.style,
247
+ arrowType: lastEdgeOp.arrowType,
248
+ bidirectional: lastEdgeOp.bidirectional,
249
+ index: edgeIndex++
250
+ });
251
+ if (lastEdgeOp.bidirectional) {
252
+ edges.push({
253
+ from: toNode.id,
254
+ to: fromNode.id,
255
+ style: lastEdgeOp.style,
256
+ arrowType: lastEdgeOp.arrowType,
257
+ bidirectional: false,
258
+ index: edgeIndex++
259
+ });
260
+ }
261
+ }
262
+ }
263
+ }
264
+ currentFromNodes = parsedNodes;
265
+ expectingEdge = false;
266
+ }
267
+ }
268
+ for (const [nodeId, className] of nodeClassAssignments) {
269
+ const node = nodes.get(nodeId);
270
+ if (node) {
271
+ node.styleClass = className;
272
+ }
273
+ }
274
+ return {
275
+ type: "flowchart",
276
+ direction: graphDirection,
277
+ nodes,
278
+ edges,
279
+ subgraphs,
280
+ styleClasses
281
+ };
282
+ }
283
+
284
+ // src/core/characters.ts
285
+ var UNICODE_CHARS = {
286
+ box: {
287
+ topLeft: "\u250C",
288
+ topRight: "\u2510",
289
+ bottomLeft: "\u2514",
290
+ bottomRight: "\u2518",
291
+ horizontal: "\u2500",
292
+ vertical: "\u2502"
293
+ },
294
+ line: {
295
+ horizontal: "\u2500",
296
+ vertical: "\u2502",
297
+ dottedHorizontal: "\u2508",
298
+ dottedVertical: "\u250A",
299
+ thickHorizontal: "\u2501",
300
+ thickVertical: "\u2503"
301
+ },
302
+ arrow: {
303
+ up: "\u25B2",
304
+ down: "\u25BC",
305
+ left: "\u25C4",
306
+ right: "\u25BA",
307
+ upOpen: "\u25B3",
308
+ downOpen: "\u25BD",
309
+ leftOpen: "\u25C1",
310
+ rightOpen: "\u25B7",
311
+ circle: "\u25CF",
312
+ cross: "\xD7"
313
+ },
314
+ junction: {
315
+ cross: "\u253C",
316
+ leftT: "\u251C",
317
+ rightT: "\u2524",
318
+ topT: "\u252C",
319
+ bottomT: "\u2534"
320
+ }
321
+ };
322
+ var ASCII_CHARS = {
323
+ box: {
324
+ topLeft: "+",
325
+ topRight: "+",
326
+ bottomLeft: "+",
327
+ bottomRight: "+",
328
+ horizontal: "-",
329
+ vertical: "|"
330
+ },
331
+ line: {
332
+ horizontal: "-",
333
+ vertical: "|",
334
+ dottedHorizontal: ".",
335
+ dottedVertical: ":",
336
+ thickHorizontal: "=",
337
+ thickVertical: "#"
338
+ },
339
+ arrow: {
340
+ up: "^",
341
+ down: "v",
342
+ left: "<",
343
+ right: ">",
344
+ upOpen: "^",
345
+ downOpen: "v",
346
+ leftOpen: "<",
347
+ rightOpen: ">",
348
+ circle: "o",
349
+ cross: "x"
350
+ },
351
+ junction: {
352
+ cross: "+",
353
+ leftT: "+",
354
+ rightT: "+",
355
+ topT: "+",
356
+ bottomT: "+"
357
+ }
358
+ };
359
+ function getCharacterSet(charset) {
360
+ return charset === "unicode" ? UNICODE_CHARS : ASCII_CHARS;
361
+ }
362
+ var JUNCTION_MERGE_MAP = {
363
+ "\u2500": { "\u2502": "\u253C", "\u250C": "\u252C", "\u2510": "\u252C", "\u2514": "\u2534", "\u2518": "\u2534", "\u251C": "\u253C", "\u2524": "\u253C", "\u252C": "\u252C", "\u2534": "\u2534" },
364
+ "\u2502": { "\u2500": "\u253C", "\u250C": "\u251C", "\u2510": "\u2524", "\u2514": "\u251C", "\u2518": "\u2524", "\u252C": "\u253C", "\u2534": "\u253C", "\u251C": "\u251C", "\u2524": "\u2524" },
365
+ "\u250C": { "\u2500": "\u252C", "\u2502": "\u251C", "\u2518": "\u253C", "\u2510": "\u252C", "\u2514": "\u251C" },
366
+ "\u2510": { "\u2500": "\u252C", "\u2502": "\u2524", "\u2514": "\u253C", "\u250C": "\u252C", "\u2518": "\u2524" },
367
+ "\u2514": { "\u2500": "\u2534", "\u2502": "\u251C", "\u2510": "\u253C", "\u250C": "\u251C", "\u2518": "\u2534" },
368
+ "\u2518": { "\u2500": "\u2534", "\u2502": "\u2524", "\u250C": "\u253C", "\u2514": "\u2534", "\u2510": "\u2524" },
369
+ "\u251C": { "\u2500": "\u253C", "\u2524": "\u253C", "\u252C": "\u253C", "\u2534": "\u253C" },
370
+ "\u2524": { "\u2500": "\u253C", "\u251C": "\u253C", "\u252C": "\u253C", "\u2534": "\u253C" },
371
+ "\u252C": { "\u2502": "\u253C", "\u251C": "\u253C", "\u2524": "\u253C", "\u2534": "\u253C" },
372
+ "\u2534": { "\u2502": "\u253C", "\u251C": "\u253C", "\u2524": "\u253C", "\u252C": "\u253C" }
373
+ };
374
+ function mergeJunctionChars(existing, incoming) {
375
+ if (existing === " ") return incoming;
376
+ if (incoming === " ") return existing;
377
+ if (existing === incoming) return existing;
378
+ const merged = JUNCTION_MERGE_MAP[existing]?.[incoming];
379
+ if (merged) return merged;
380
+ const reverseMerged = JUNCTION_MERGE_MAP[incoming]?.[existing];
381
+ if (reverseMerged) return reverseMerged;
382
+ return incoming;
383
+ }
384
+ function isLineOrJunctionChar(char) {
385
+ return "\u2500\u2502\u251C\u2524\u252C\u2534\u253C\u2508\u250A\u2501\u2503-|+".includes(char);
386
+ }
387
+ function isJunctionChar(char) {
388
+ return "\u2500\u2502\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u2508\u250A\u2501\u2503-|+".includes(char);
389
+ }
390
+ function isHorizontalChar(char) {
391
+ return "\u2500\u2508\u2501-\u2550".includes(char);
392
+ }
393
+ function isVerticalChar(char) {
394
+ return "\u2502\u250A\u2503|\u2551".includes(char);
395
+ }
396
+ function connectsLeft(char) {
397
+ return "\u2500\u2508\u2501-\u2550\u253C\u2534\u252C\u2524\u2518\u2510\u251C\u2514\u250C+\u25C4<\u25C1".includes(char);
398
+ }
399
+ function connectsRight(char) {
400
+ return "\u2500\u2508\u2501-\u2550\u253C\u2534\u252C\u251C\u2518\u2510\u2524\u2514\u250C+\u25BA>\u25B7".includes(char);
401
+ }
402
+ function connectsUp(char) {
403
+ return "\u2502\u250A\u2503|\u2551\u253C\u251C\u2524\u2534\u2518\u2514+\u25B2^\u25B3".includes(char);
404
+ }
405
+ function connectsDown(char) {
406
+ return "\u2502\u250A\u2503|\u2551\u253C\u251C\u2524\u252C\u2510\u250C+\u25BCv\u25BD".includes(char);
407
+ }
408
+ function getJunctionChar(up, down, left, right, charset) {
409
+ const chars = getCharacterSet(charset);
410
+ const count = [up, down, left, right].filter(Boolean).length;
411
+ if (count === 4) return chars.junction.cross;
412
+ if (count === 3) {
413
+ if (!up) return chars.junction.topT;
414
+ if (!down) return chars.junction.bottomT;
415
+ if (!left) return chars.junction.leftT;
416
+ if (!right) return chars.junction.rightT;
417
+ }
418
+ if (count === 2) {
419
+ if (up && down) return chars.line.vertical;
420
+ if (left && right) return chars.line.horizontal;
421
+ if (down && right) return chars.box.topLeft;
422
+ if (down && left) return chars.box.topRight;
423
+ if (up && right) return chars.box.bottomLeft;
424
+ if (up && left) return chars.box.bottomRight;
425
+ }
426
+ if (count === 1) {
427
+ if (up || down) return chars.line.vertical;
428
+ if (left || right) return chars.line.horizontal;
429
+ }
430
+ return " ";
431
+ }
432
+
433
+ // src/core/canvas.ts
434
+ var Canvas = class {
435
+ constructor(width, height, charset = "unicode") {
436
+ this.width = width;
437
+ this.height = height;
438
+ this.grid = Array.from(
439
+ { length: height },
440
+ () => Array.from({ length: width }, () => " ")
441
+ );
442
+ this.chars = getCharacterSet(charset);
443
+ }
444
+ grid;
445
+ chars;
446
+ getWidth() {
447
+ return this.width;
448
+ }
449
+ getHeight() {
450
+ return this.height;
451
+ }
452
+ set(x, y, char) {
453
+ if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
454
+ const existing = this.grid[y][x];
455
+ if (isLineOrJunctionChar(existing) && isLineOrJunctionChar(char)) {
456
+ this.grid[y][x] = mergeJunctionChars(existing, char);
457
+ } else if (existing === " " || !isLineOrJunctionChar(existing)) {
458
+ this.grid[y][x] = char;
459
+ }
460
+ }
461
+ }
462
+ forceSet(x, y, char) {
463
+ if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
464
+ this.grid[y][x] = char;
465
+ }
466
+ }
467
+ get(x, y) {
468
+ if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
469
+ return this.grid[y][x];
470
+ }
471
+ return " ";
472
+ }
473
+ drawText(x, y, text) {
474
+ for (let i = 0; i < text.length; i++) {
475
+ if (x + i < this.width) {
476
+ this.grid[y][x + i] = text[i];
477
+ }
478
+ }
479
+ }
480
+ drawHorizontalLine(x1, x2, y, char) {
481
+ const lineChar = char ?? this.chars.line.horizontal;
482
+ const startX = Math.min(x1, x2);
483
+ const endX = Math.max(x1, x2);
484
+ for (let x = startX; x <= endX; x++) {
485
+ this.set(x, y, lineChar);
486
+ }
487
+ }
488
+ drawVerticalLine(x, y1, y2, char) {
489
+ const lineChar = char ?? this.chars.line.vertical;
490
+ const startY = Math.min(y1, y2);
491
+ const endY = Math.max(y1, y2);
492
+ for (let y = startY; y <= endY; y++) {
493
+ this.set(x, y, lineChar);
494
+ }
495
+ }
496
+ drawBox(x, y, width, height) {
497
+ const { topLeft, topRight, bottomLeft, bottomRight, horizontal, vertical } = this.chars.box;
498
+ this.set(x, y, topLeft);
499
+ this.set(x + width - 1, y, topRight);
500
+ this.set(x, y + height - 1, bottomLeft);
501
+ this.set(x + width - 1, y + height - 1, bottomRight);
502
+ for (let i = 1; i < width - 1; i++) {
503
+ this.set(x + i, y, horizontal);
504
+ this.set(x + i, y + height - 1, horizontal);
505
+ }
506
+ for (let i = 1; i < height - 1; i++) {
507
+ this.set(x, y + i, vertical);
508
+ this.set(x + width - 1, y + i, vertical);
509
+ }
510
+ }
511
+ drawArrow(x, y, direction) {
512
+ this.set(x, y, this.chars.arrow[direction]);
513
+ }
514
+ expandTo(newWidth, newHeight) {
515
+ if (newWidth > this.width) {
516
+ for (const row of this.grid) {
517
+ while (row.length < newWidth) {
518
+ row.push(" ");
519
+ }
520
+ }
521
+ this.width = newWidth;
522
+ }
523
+ if (newHeight > this.height) {
524
+ while (this.grid.length < newHeight) {
525
+ this.grid.push(Array.from({ length: this.width }, () => " "));
526
+ }
527
+ this.height = newHeight;
528
+ }
529
+ }
530
+ merge(other, offsetX, offsetY) {
531
+ const requiredWidth = offsetX + other.getWidth();
532
+ const requiredHeight = offsetY + other.getHeight();
533
+ this.expandTo(requiredWidth, requiredHeight);
534
+ for (let y = 0; y < other.getHeight(); y++) {
535
+ for (let x = 0; x < other.getWidth(); x++) {
536
+ const char = other.get(x, y);
537
+ if (char !== " ") {
538
+ this.set(offsetX + x, offsetY + y, char);
539
+ }
540
+ }
541
+ }
542
+ }
543
+ fixJunctions() {
544
+ const junctionChars = "\u253C\u252C\u2534\u251C\u2524+";
545
+ for (let y = 0; y < this.height; y++) {
546
+ for (let x = 0; x < this.width; x++) {
547
+ const char = this.grid[y][x];
548
+ if (!junctionChars.includes(char)) continue;
549
+ const above = y > 0 ? this.grid[y - 1][x] : " ";
550
+ const below = y < this.height - 1 ? this.grid[y + 1][x] : " ";
551
+ const left = x > 0 ? this.grid[y][x - 1] : " ";
552
+ const right = x < this.width - 1 ? this.grid[y][x + 1] : " ";
553
+ const hasUp = connectsDown(above);
554
+ const hasDown = connectsUp(below);
555
+ const hasLeft = connectsRight(left);
556
+ const hasRight = connectsLeft(right);
557
+ const charset = this.chars === getCharacterSet("unicode") ? "unicode" : "ascii";
558
+ const corrected = getJunctionChar(hasUp, hasDown, hasLeft, hasRight, charset);
559
+ if (corrected !== char) {
560
+ this.grid[y][x] = corrected;
561
+ }
562
+ }
563
+ }
564
+ }
565
+ toString() {
566
+ this.fixJunctions();
567
+ return this.grid.map((row) => row.join("").trimEnd()).join("\n").trimEnd();
568
+ }
569
+ static fromCoord(coord) {
570
+ return { x: coord.x, y: coord.y };
571
+ }
572
+ };
573
+
574
+ // src/layout/sugiyama.ts
575
+ function getOutgoingEdges(nodeId, edges) {
576
+ return edges.filter((e) => e.from === nodeId);
577
+ }
578
+ function getIncomingEdges(nodeId, edges) {
579
+ return edges.filter((e) => e.to === nodeId);
580
+ }
581
+ function detectCyclesAndBackEdges(nodes, edges) {
582
+ const backEdges = /* @__PURE__ */ new Set();
583
+ const visited = /* @__PURE__ */ new Set();
584
+ const inStack = /* @__PURE__ */ new Set();
585
+ function dfs(nodeId) {
586
+ visited.add(nodeId);
587
+ inStack.add(nodeId);
588
+ for (const edge of getOutgoingEdges(nodeId, edges)) {
589
+ if (inStack.has(edge.to)) {
590
+ backEdges.add(edge);
591
+ } else if (!visited.has(edge.to)) {
592
+ dfs(edge.to);
593
+ }
594
+ }
595
+ inStack.delete(nodeId);
596
+ }
597
+ for (const nodeId of nodes.keys()) {
598
+ if (!visited.has(nodeId)) {
599
+ dfs(nodeId);
600
+ }
601
+ }
602
+ const forwardEdges = edges.filter((e) => !backEdges.has(e));
603
+ return { backEdges, forwardEdges };
604
+ }
605
+ function assignLayers(nodes, forwardEdges) {
606
+ const layers = /* @__PURE__ */ new Map();
607
+ const inDegree = /* @__PURE__ */ new Map();
608
+ for (const nodeId of nodes.keys()) {
609
+ inDegree.set(nodeId, 0);
610
+ }
611
+ for (const edge of forwardEdges) {
612
+ inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
613
+ }
614
+ const queue = [];
615
+ for (const [nodeId, degree] of inDegree) {
616
+ if (degree === 0) {
617
+ queue.push(nodeId);
618
+ layers.set(nodeId, 0);
619
+ }
620
+ }
621
+ while (queue.length > 0) {
622
+ const nodeId = queue.shift();
623
+ const currentLayer = layers.get(nodeId) ?? 0;
624
+ for (const edge of getOutgoingEdges(nodeId, forwardEdges)) {
625
+ const targetLayer = Math.max(layers.get(edge.to) ?? 0, currentLayer + 1);
626
+ layers.set(edge.to, targetLayer);
627
+ const newDegree = (inDegree.get(edge.to) ?? 1) - 1;
628
+ inDegree.set(edge.to, newDegree);
629
+ if (newDegree === 0) {
630
+ queue.push(edge.to);
631
+ }
632
+ }
633
+ }
634
+ for (const nodeId of nodes.keys()) {
635
+ if (!layers.has(nodeId)) {
636
+ layers.set(nodeId, 0);
637
+ }
638
+ }
639
+ return layers;
640
+ }
641
+ function orderNodesInLayers(nodes, edges, layers, backEdges) {
642
+ const layerNodes = /* @__PURE__ */ new Map();
643
+ for (const [nodeId, layer] of layers) {
644
+ if (!layerNodes.has(layer)) {
645
+ layerNodes.set(layer, []);
646
+ }
647
+ layerNodes.get(layer).push(nodeId);
648
+ }
649
+ const forwardEdges = edges.filter((e) => !backEdges.has(e));
650
+ for (let iteration = 0; iteration < 4; iteration++) {
651
+ const sortedLayers = [...layerNodes.keys()].sort((a, b) => a - b);
652
+ for (const layer of sortedLayers) {
653
+ if (layer === 0) {
654
+ const nodeIds2 = layerNodes.get(layer);
655
+ nodeIds2.sort((a, b) => (nodes.get(a)?.index ?? 0) - (nodes.get(b)?.index ?? 0));
656
+ continue;
657
+ }
658
+ const nodeIds = layerNodes.get(layer);
659
+ const prevLayerNodes = layerNodes.get(layer - 1) ?? [];
660
+ const prevPositions = new Map(prevLayerNodes.map((id, idx) => [id, idx]));
661
+ nodeIds.sort((a, b) => {
662
+ const aIncoming = getIncomingEdges(a, forwardEdges);
663
+ const bIncoming = getIncomingEdges(b, forwardEdges);
664
+ const aBarycenter = aIncoming.length > 0 ? aIncoming.reduce((sum, e) => sum + (prevPositions.get(e.from) ?? 0), 0) / aIncoming.length : nodes.get(a)?.index ?? 0;
665
+ const bBarycenter = bIncoming.length > 0 ? bIncoming.reduce((sum, e) => sum + (prevPositions.get(e.from) ?? 0), 0) / bIncoming.length : nodes.get(b)?.index ?? 0;
666
+ return aBarycenter - bBarycenter;
667
+ });
668
+ }
669
+ }
670
+ return layerNodes;
671
+ }
672
+ function assignGridCoordinates(nodes, layerNodes, direction, _options) {
673
+ const isHorizontal = direction === "LR" || direction === "RL";
674
+ const isReversed = direction === "RL" || direction === "BT";
675
+ const spacing = 1;
676
+ const maxLayer = Math.max(...layerNodes.keys(), 0);
677
+ for (const [layer, nodeIds] of layerNodes) {
678
+ const actualLayer = isReversed ? maxLayer - layer : layer;
679
+ for (let pos = 0; pos < nodeIds.length; pos++) {
680
+ const nodeId = nodeIds[pos];
681
+ const node = nodes.get(nodeId);
682
+ if (!node) continue;
683
+ if (isHorizontal) {
684
+ node.gridCoord = {
685
+ x: actualLayer * spacing,
686
+ y: pos * spacing
687
+ };
688
+ } else {
689
+ node.gridCoord = {
690
+ x: pos * spacing,
691
+ y: actualLayer * spacing
692
+ };
693
+ }
694
+ }
695
+ }
696
+ }
697
+ function calculateDimensions(nodes, options) {
698
+ const columnWidths = /* @__PURE__ */ new Map();
699
+ const rowHeights = /* @__PURE__ */ new Map();
700
+ for (const node of nodes.values()) {
701
+ if (!node.gridCoord) continue;
702
+ const labelWidth = node.label.length + options.borderPadding * 2 + 2;
703
+ const labelHeight = 1 + options.borderPadding * 2 + 2;
704
+ node.width = labelWidth;
705
+ node.height = labelHeight;
706
+ const col = node.gridCoord.x;
707
+ const row = node.gridCoord.y;
708
+ const currentColWidth = columnWidths.get(col) ?? 0;
709
+ columnWidths.set(col, Math.max(currentColWidth, labelWidth));
710
+ const currentRowHeight = rowHeights.get(row) ?? 0;
711
+ rowHeights.set(row, Math.max(currentRowHeight, labelHeight));
712
+ }
713
+ const maxCol = columnWidths.size > 0 ? Math.max(...columnWidths.keys()) : 0;
714
+ const maxRow = rowHeights.size > 0 ? Math.max(...rowHeights.keys()) : 0;
715
+ for (let i = 0; i <= maxCol; i++) {
716
+ if (!columnWidths.has(i)) {
717
+ columnWidths.set(i, options.paddingX);
718
+ }
719
+ }
720
+ for (let i = 0; i <= maxRow; i++) {
721
+ if (!rowHeights.has(i)) {
722
+ rowHeights.set(i, options.paddingY);
723
+ }
724
+ }
725
+ return { columnWidths, rowHeights };
726
+ }
727
+ function gridToDrawingCoord(gridCoord, columnWidths, rowHeights, paddingX, paddingY) {
728
+ let x = 0;
729
+ for (let col = 0; col < gridCoord.x; col++) {
730
+ x += (columnWidths.get(col) ?? 0) + paddingX;
731
+ }
732
+ let y = 0;
733
+ for (let row = 0; row < gridCoord.y; row++) {
734
+ y += (rowHeights.get(row) ?? 0) + paddingY;
735
+ }
736
+ return { x, y };
737
+ }
738
+ function layoutFlowchart(ast, options) {
739
+ const { nodes, edges, direction } = ast;
740
+ if (nodes.size === 0) {
741
+ return {
742
+ nodes,
743
+ edges,
744
+ backEdges: /* @__PURE__ */ new Set(),
745
+ width: 0,
746
+ height: 0,
747
+ columnWidths: /* @__PURE__ */ new Map(),
748
+ rowHeights: /* @__PURE__ */ new Map()
749
+ };
750
+ }
751
+ const { backEdges, forwardEdges } = detectCyclesAndBackEdges(nodes, edges);
752
+ const layers = assignLayers(nodes, forwardEdges);
753
+ const layerNodes = orderNodesInLayers(nodes, edges, layers, backEdges);
754
+ assignGridCoordinates(nodes, layerNodes, direction);
755
+ const { columnWidths, rowHeights } = calculateDimensions(nodes, options);
756
+ for (const node of nodes.values()) {
757
+ if (node.gridCoord) {
758
+ const baseCoord = gridToDrawingCoord(
759
+ node.gridCoord,
760
+ columnWidths,
761
+ rowHeights,
762
+ options.paddingX,
763
+ options.paddingY
764
+ );
765
+ const colWidth = columnWidths.get(node.gridCoord.x) ?? 0;
766
+ const rowHeight = rowHeights.get(node.gridCoord.y) ?? 0;
767
+ const nodeWidth = node.width ?? 0;
768
+ const nodeHeight = node.height ?? 0;
769
+ node.drawingCoord = {
770
+ x: baseCoord.x + Math.floor((colWidth - nodeWidth) / 2),
771
+ y: baseCoord.y + Math.floor((rowHeight - nodeHeight) / 2)
772
+ };
773
+ }
774
+ }
775
+ let totalWidth = 0;
776
+ for (const width of columnWidths.values()) {
777
+ totalWidth += width + options.paddingX;
778
+ }
779
+ let totalHeight = 0;
780
+ for (const height of rowHeights.values()) {
781
+ totalHeight += height + options.paddingY;
782
+ }
783
+ return {
784
+ nodes,
785
+ edges,
786
+ backEdges,
787
+ width: totalWidth,
788
+ height: totalHeight,
789
+ columnWidths,
790
+ rowHeights
791
+ };
792
+ }
793
+
794
+ // src/diagrams/flowchart/renderer.ts
795
+ function drawNode(canvas, node) {
796
+ if (!node.drawingCoord || !node.width || !node.height) return;
797
+ const { x, y } = node.drawingCoord;
798
+ const { width, height } = node;
799
+ canvas.drawBox(x, y, width, height);
800
+ const textX = x + Math.floor((width - node.label.length) / 2);
801
+ const textY = y + Math.floor(height / 2);
802
+ canvas.drawText(textX, textY, node.label);
803
+ }
804
+ function getNodeCenter(node) {
805
+ if (!node.drawingCoord || !node.width || !node.height) {
806
+ return { x: 0, y: 0 };
807
+ }
808
+ const { x, y } = node.drawingCoord;
809
+ return {
810
+ x: x + Math.floor(node.width / 2),
811
+ y: y + Math.floor(node.height / 2)
812
+ };
813
+ }
814
+ function getNodeBorderPoint(node, direction) {
815
+ if (!node.drawingCoord || !node.width || !node.height) {
816
+ return { x: 0, y: 0 };
817
+ }
818
+ const { x, y } = node.drawingCoord;
819
+ const { width, height } = node;
820
+ const centerX = x + Math.floor(width / 2);
821
+ const centerY = y + Math.floor(height / 2);
822
+ switch (direction) {
823
+ case "right":
824
+ return { x: x + width - 1, y: centerY };
825
+ case "left":
826
+ return { x, y: centerY };
827
+ case "down":
828
+ return { x: centerX, y: y + height - 1 };
829
+ case "up":
830
+ return { x: centerX, y };
831
+ }
832
+ }
833
+ function determineEdgeDirection(fromNode, toNode, graphDirection) {
834
+ const fromCenter = getNodeCenter(fromNode);
835
+ const toCenter = getNodeCenter(toNode);
836
+ const dx = toCenter.x - fromCenter.x;
837
+ const dy = toCenter.y - fromCenter.y;
838
+ switch (graphDirection) {
839
+ case "LR":
840
+ if (dx > 0) return { startDir: "right", endDir: "left" };
841
+ if (dx < 0) return { startDir: "left", endDir: "right" };
842
+ return dy > 0 ? { startDir: "down", endDir: "up" } : { startDir: "up", endDir: "down" };
843
+ case "RL":
844
+ if (dx < 0) return { startDir: "left", endDir: "right" };
845
+ if (dx > 0) return { startDir: "right", endDir: "left" };
846
+ return dy > 0 ? { startDir: "down", endDir: "up" } : { startDir: "up", endDir: "down" };
847
+ case "TD":
848
+ if (dy > 0) return { startDir: "down", endDir: "up" };
849
+ if (dy < 0) return { startDir: "up", endDir: "down" };
850
+ return dx > 0 ? { startDir: "right", endDir: "left" } : { startDir: "left", endDir: "right" };
851
+ case "BT":
852
+ if (dy < 0) return { startDir: "up", endDir: "down" };
853
+ if (dy > 0) return { startDir: "down", endDir: "up" };
854
+ return dx > 0 ? { startDir: "right", endDir: "left" } : { startDir: "left", endDir: "right" };
855
+ }
856
+ }
857
+ function drawBackEdge(canvas, edge, fromNode, toNode, graphDirection, options) {
858
+ const chars = getCharacterSet(options.charset);
859
+ const isVertical = graphDirection === "TD" || graphDirection === "BT";
860
+ const hChar = edge.style === "dotted" ? chars.line.dottedHorizontal : edge.style === "thick" ? chars.line.thickHorizontal : chars.line.horizontal;
861
+ const vChar = edge.style === "dotted" ? chars.line.dottedVertical : edge.style === "thick" ? chars.line.thickVertical : chars.line.vertical;
862
+ if (isVertical) {
863
+ const fromRight = getNodeBorderPoint(fromNode, "right");
864
+ const toRight = getNodeBorderPoint(toNode, "right");
865
+ const loopX = Math.max(fromRight.x, toRight.x) + 3;
866
+ canvas.drawHorizontalLine(fromRight.x + 1, loopX, fromRight.y, hChar);
867
+ canvas.drawVerticalLine(loopX, Math.min(fromRight.y, toRight.y), Math.max(fromRight.y, toRight.y), vChar);
868
+ canvas.drawHorizontalLine(toRight.x + 1, loopX, toRight.y, hChar);
869
+ } else {
870
+ const fromBottom = getNodeBorderPoint(fromNode, "down");
871
+ const toBottom = getNodeBorderPoint(toNode, "down");
872
+ const loopY = Math.max(fromBottom.y, toBottom.y) + 2;
873
+ canvas.drawVerticalLine(fromBottom.x, fromBottom.y + 1, loopY, vChar);
874
+ canvas.drawHorizontalLine(Math.min(fromBottom.x, toBottom.x), Math.max(fromBottom.x, toBottom.x), loopY, hChar);
875
+ canvas.drawVerticalLine(toBottom.x, toBottom.y + 1, loopY, vChar);
876
+ }
877
+ }
878
+ function drawEdge(canvas, edge, fromNode, toNode, graphDirection, options, isBackEdge = false, sourceIndex = 0, totalFromSource = 1, targetIndex = 0, totalToTarget = 1) {
879
+ if (isBackEdge) {
880
+ drawBackEdge(canvas, edge, fromNode, toNode, graphDirection, options);
881
+ return;
882
+ }
883
+ const chars = getCharacterSet(options.charset);
884
+ const { startDir, endDir } = determineEdgeDirection(fromNode, toNode, graphDirection);
885
+ const startBorder = getNodeBorderPoint(fromNode, startDir);
886
+ const endBorder = getNodeBorderPoint(toNode, endDir);
887
+ const hChar = edge.style === "dotted" ? chars.line.dottedHorizontal : edge.style === "thick" ? chars.line.thickHorizontal : chars.line.horizontal;
888
+ const vChar = edge.style === "dotted" ? chars.line.dottedVertical : edge.style === "thick" ? chars.line.thickVertical : chars.line.vertical;
889
+ let startX = startBorder.x;
890
+ let startY = startBorder.y;
891
+ let endX = endBorder.x;
892
+ let endY = endBorder.y;
893
+ if (startDir === "right") startX += 1;
894
+ else if (startDir === "left") startX -= 1;
895
+ else if (startDir === "down") startY += 1;
896
+ else if (startDir === "up") startY -= 1;
897
+ const dx = Math.abs(endX - startX);
898
+ const dy = Math.abs(endY - startY);
899
+ const isVerticalPrimary = startDir === "down" || startDir === "up";
900
+ const isHorizontalPrimary = startDir === "left" || startDir === "right";
901
+ const alignmentTolerance = 3;
902
+ const effectivelyAlignedX = dx <= alignmentTolerance && isVerticalPrimary;
903
+ const effectivelyAlignedY = dy <= alignmentTolerance && isHorizontalPrimary;
904
+ if (startY === endY || effectivelyAlignedY) {
905
+ canvas.drawHorizontalLine(Math.min(startX, endX), Math.max(startX, endX), startY, hChar);
906
+ } else if (startX === endX || effectivelyAlignedX) {
907
+ canvas.drawVerticalLine(endX, Math.min(startY, endY), Math.max(startY, endY), vChar);
908
+ } else {
909
+ const isVerticalFirst = startDir === "down" || startDir === "up";
910
+ if (isVerticalFirst) {
911
+ const sourceOffset = totalFromSource > 1 ? (sourceIndex - Math.floor(totalFromSource / 2)) * 2 : 0;
912
+ const baseTurnY = endY + (endDir === "up" ? -2 : 2);
913
+ const turnY = totalToTarget > 1 ? baseTurnY : baseTurnY + sourceOffset;
914
+ canvas.drawVerticalLine(startX, Math.min(startY, turnY), Math.max(startY, turnY), vChar);
915
+ canvas.drawHorizontalLine(Math.min(startX, endX), Math.max(startX, endX), turnY, hChar);
916
+ if (targetIndex === 0) {
917
+ canvas.drawVerticalLine(endX, Math.min(turnY, endY), Math.max(turnY, endY), vChar);
918
+ }
919
+ } else {
920
+ const sourceOffset = totalFromSource > 1 ? (sourceIndex - Math.floor(totalFromSource / 2)) * 2 : 0;
921
+ const baseTurnX = endX + (endDir === "left" ? -2 : 2);
922
+ const turnX = totalToTarget > 1 ? baseTurnX : baseTurnX + sourceOffset;
923
+ canvas.drawHorizontalLine(Math.min(startX, turnX), Math.max(startX, turnX), startY, hChar);
924
+ canvas.drawVerticalLine(turnX, Math.min(startY, endY), Math.max(startY, endY), vChar);
925
+ if (targetIndex === 0) {
926
+ canvas.drawHorizontalLine(Math.min(turnX, endX), Math.max(turnX, endX), endY, hChar);
927
+ }
928
+ }
929
+ }
930
+ if (edge.label) {
931
+ const isHorizontalEdge = startY === endY || effectivelyAlignedY;
932
+ const isVerticalEdge = startX === endX || effectivelyAlignedX;
933
+ if (isHorizontalEdge) {
934
+ const availableSpace = Math.abs(endX - startX) - 2;
935
+ if (availableSpace >= edge.label.length) {
936
+ const midX = Math.floor((startX + endX) / 2);
937
+ const labelX = midX - Math.floor(edge.label.length / 2);
938
+ canvas.drawText(labelX, startY - 1, edge.label);
939
+ }
940
+ } else if (isVerticalEdge) {
941
+ const midY = Math.floor((startY + endY) / 2);
942
+ canvas.drawText(endX + 2, midY, edge.label);
943
+ } else {
944
+ const isVerticalFirst = startDir === "down" || startDir === "up";
945
+ if (isVerticalFirst) {
946
+ const labelX = endX + 1;
947
+ const labelY = endY - 2;
948
+ canvas.drawText(labelX, labelY, edge.label);
949
+ } else {
950
+ const labelY = Math.min(startY, endY) + Math.floor(Math.abs(endY - startY) / 2);
951
+ const labelX = endX + 2;
952
+ canvas.drawText(labelX, labelY, edge.label);
953
+ }
954
+ }
955
+ }
956
+ }
957
+ function renderFlowchart(ast, options) {
958
+ const layout = layoutFlowchart(ast, options);
959
+ if (layout.nodes.size === 0) {
960
+ return "";
961
+ }
962
+ const canvas = new Canvas(layout.width + 10, layout.height + 10, options.charset);
963
+ const edgesBySource = /* @__PURE__ */ new Map();
964
+ const edgesByTarget = /* @__PURE__ */ new Map();
965
+ for (const edge of layout.edges) {
966
+ const fromList = edgesBySource.get(edge.from) ?? [];
967
+ fromList.push(edge);
968
+ edgesBySource.set(edge.from, fromList);
969
+ const toList = edgesByTarget.get(edge.to) ?? [];
970
+ toList.push(edge);
971
+ edgesByTarget.set(edge.to, toList);
972
+ }
973
+ for (const edge of layout.edges) {
974
+ const fromNode = layout.nodes.get(edge.from);
975
+ const toNode = layout.nodes.get(edge.to);
976
+ if (fromNode && toNode) {
977
+ const isBackEdge = layout.backEdges.has(edge);
978
+ const edgesFromSameSource = edgesBySource.get(edge.from) ?? [];
979
+ const edgesToSameTarget = edgesByTarget.get(edge.to) ?? [];
980
+ const sourceIndex = edgesFromSameSource.indexOf(edge);
981
+ const targetIndex = edgesToSameTarget.indexOf(edge);
982
+ drawEdge(
983
+ canvas,
984
+ edge,
985
+ fromNode,
986
+ toNode,
987
+ ast.direction,
988
+ options,
989
+ isBackEdge,
990
+ sourceIndex,
991
+ edgesFromSameSource.length,
992
+ targetIndex,
993
+ edgesToSameTarget.length
994
+ );
995
+ }
996
+ }
997
+ for (const node of layout.nodes.values()) {
998
+ drawNode(canvas, node);
999
+ }
1000
+ for (const edge of layout.edges) {
1001
+ const toNode = layout.nodes.get(edge.to);
1002
+ if (toNode && edge.arrowType !== "open") {
1003
+ const isBackEdge = layout.backEdges.has(edge);
1004
+ const chars = getCharacterSet(options.charset);
1005
+ let endDir;
1006
+ if (isBackEdge) {
1007
+ const isVertical = ast.direction === "TD" || ast.direction === "BT";
1008
+ endDir = isVertical ? "right" : "down";
1009
+ } else {
1010
+ endDir = determineEdgeDirection(
1011
+ layout.nodes.get(edge.from),
1012
+ toNode,
1013
+ ast.direction
1014
+ ).endDir;
1015
+ }
1016
+ const endBorder = getNodeBorderPoint(toNode, endDir);
1017
+ const arrowChar = edge.arrowType === "arrow" ? endDir === "left" ? chars.arrow.right : endDir === "right" ? chars.arrow.left : endDir === "up" ? chars.arrow.down : chars.arrow.up : edge.arrowType === "circle" ? chars.arrow.circle : chars.arrow.cross;
1018
+ canvas.forceSet(endBorder.x, endBorder.y, arrowChar);
1019
+ }
1020
+ }
1021
+ return canvas.toString();
1022
+ }
1023
+
1024
+ // src/diagrams/sequence/parser.ts
1025
+ var SEQUENCE_HEADER = /^sequenceDiagram\s*$/i;
1026
+ var PARTICIPANT_PATTERN = /^participant\s+(\S+)(?:\s+as\s+(.+))?$/i;
1027
+ var ACTOR_PATTERN = /^actor\s+(\S+)(?:\s+as\s+(.+))?$/i;
1028
+ var MESSAGE_PATTERNS = [
1029
+ // Order matters: longer arrow patterns must come before shorter ones
1030
+ { pattern: /^(\w+)\s*->>>\s*(\w+)\s*:\s*(.*)$/, style: "solid", arrowStyle: "filled" },
1031
+ { pattern: /^(\w+)\s*-->>>\s*(\w+)\s*:\s*(.*)$/, style: "dotted", arrowStyle: "filled" },
1032
+ { pattern: /^(\w+)\s*->>\s*(\w+)\s*:\s*(.*)$/, style: "solid", arrowStyle: "filled" },
1033
+ { pattern: /^(\w+)\s*-->>\s*(\w+)\s*:\s*(.*)$/, style: "dotted", arrowStyle: "filled" },
1034
+ { pattern: /^(\w+)\s*-->\s*(\w+)\s*:\s*(.*)$/, style: "dotted", arrowStyle: "open" },
1035
+ { pattern: /^(\w+)\s*->\s*(\w+)\s*:\s*(.*)$/, style: "solid", arrowStyle: "open" },
1036
+ { pattern: /^(\w+)\s*--x\s*(\w+)\s*:\s*(.*)$/, style: "dotted", arrowStyle: "filled" },
1037
+ { pattern: /^(\w+)\s*-x\s*(\w+)\s*:\s*(.*)$/, style: "solid", arrowStyle: "filled" }
1038
+ ];
1039
+ function getOrCreateParticipant(participants, id, alias) {
1040
+ let participant = participants.get(id);
1041
+ if (!participant) {
1042
+ participant = {
1043
+ id,
1044
+ alias: alias ?? id,
1045
+ index: participants.size
1046
+ };
1047
+ participants.set(id, participant);
1048
+ } else if (alias && participant.alias === participant.id) {
1049
+ participant.alias = alias;
1050
+ }
1051
+ return participant;
1052
+ }
1053
+ function parseSequence(input) {
1054
+ const lines = input.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("%%"));
1055
+ if (lines.length === 0) {
1056
+ throw new Error("Empty sequence diagram input");
1057
+ }
1058
+ if (!SEQUENCE_HEADER.test(lines[0])) {
1059
+ throw new Error(`Invalid sequence diagram header. Expected "sequenceDiagram", got: "${lines[0]}"`);
1060
+ }
1061
+ const participants = /* @__PURE__ */ new Map();
1062
+ const messages = [];
1063
+ let messageIndex = 0;
1064
+ for (let i = 1; i < lines.length; i++) {
1065
+ const line = lines[i];
1066
+ if (line.startsWith("Note ") || line.startsWith("note ")) continue;
1067
+ if (line.startsWith("loop ") || line.startsWith("alt ") || line.startsWith("opt ")) continue;
1068
+ if (line === "end") continue;
1069
+ if (line.startsWith("rect ")) continue;
1070
+ if (line.startsWith("activate ") || line.startsWith("deactivate ")) continue;
1071
+ const participantMatch = line.match(PARTICIPANT_PATTERN);
1072
+ if (participantMatch) {
1073
+ const [, id, alias] = participantMatch;
1074
+ getOrCreateParticipant(participants, id, alias);
1075
+ continue;
1076
+ }
1077
+ const actorMatch = line.match(ACTOR_PATTERN);
1078
+ if (actorMatch) {
1079
+ const [, id, alias] = actorMatch;
1080
+ getOrCreateParticipant(participants, id, alias);
1081
+ continue;
1082
+ }
1083
+ for (const { pattern, style, arrowStyle } of MESSAGE_PATTERNS) {
1084
+ const match = line.match(pattern);
1085
+ if (match) {
1086
+ const [, from, to, text] = match;
1087
+ getOrCreateParticipant(participants, from);
1088
+ getOrCreateParticipant(participants, to);
1089
+ messages.push({
1090
+ from,
1091
+ to,
1092
+ text: text.trim(),
1093
+ style,
1094
+ arrowStyle,
1095
+ index: messageIndex++
1096
+ });
1097
+ break;
1098
+ }
1099
+ }
1100
+ }
1101
+ return {
1102
+ type: "sequence",
1103
+ participants,
1104
+ messages
1105
+ };
1106
+ }
1107
+
1108
+ // src/diagrams/sequence/renderer.ts
1109
+ var PARTICIPANT_BOX_HEIGHT = 3;
1110
+ var PARTICIPANT_PADDING = 2;
1111
+ var PARTICIPANT_GAP = 6;
1112
+ var MESSAGE_ROW_HEIGHT = 2;
1113
+ var LIFELINE_START_OFFSET = 2;
1114
+ function layoutSequence(ast, _options) {
1115
+ const participantList = Array.from(ast.participants.values()).sort((a, b) => a.index - b.index);
1116
+ const layouts = [];
1117
+ const participantMap = /* @__PURE__ */ new Map();
1118
+ let currentX = PARTICIPANT_PADDING;
1119
+ for (const participant of participantList) {
1120
+ const labelWidth = participant.alias.length;
1121
+ const boxWidth = Math.max(labelWidth + 4, 7);
1122
+ const layout = {
1123
+ participant,
1124
+ x: currentX,
1125
+ width: boxWidth,
1126
+ centerX: currentX + Math.floor(boxWidth / 2)
1127
+ };
1128
+ layouts.push(layout);
1129
+ participantMap.set(participant.id, layout);
1130
+ currentX += boxWidth + PARTICIPANT_GAP;
1131
+ }
1132
+ const totalWidth = currentX - PARTICIPANT_GAP + PARTICIPANT_PADDING;
1133
+ const lifelineHeight = ast.messages.length * MESSAGE_ROW_HEIGHT + LIFELINE_START_OFFSET;
1134
+ const totalHeight = PARTICIPANT_BOX_HEIGHT + lifelineHeight + PARTICIPANT_BOX_HEIGHT + 2;
1135
+ return {
1136
+ participants: layouts,
1137
+ participantMap,
1138
+ width: totalWidth,
1139
+ height: totalHeight
1140
+ };
1141
+ }
1142
+ function drawParticipantBox(canvas, layout, y) {
1143
+ canvas.drawBox(layout.x, y, layout.width, PARTICIPANT_BOX_HEIGHT);
1144
+ const textX = layout.x + Math.floor((layout.width - layout.participant.alias.length) / 2);
1145
+ const textY = y + 1;
1146
+ canvas.drawText(textX, textY, layout.participant.alias);
1147
+ }
1148
+ function drawLifeline(canvas, layout, startY, endY, chars) {
1149
+ const x = layout.centerX;
1150
+ for (let y = startY; y <= endY; y++) {
1151
+ canvas.set(x, y, chars.line.vertical);
1152
+ }
1153
+ }
1154
+ function drawMessage(canvas, message, fromLayout, toLayout, y, chars) {
1155
+ const fromX = fromLayout.centerX;
1156
+ const toX = toLayout.centerX;
1157
+ const isSelfMessage = fromLayout.participant.id === toLayout.participant.id;
1158
+ if (isSelfMessage) {
1159
+ const loopWidth = 4;
1160
+ const lineChar2 = message.style === "dotted" ? chars.line.dottedHorizontal : chars.line.horizontal;
1161
+ const vChar = message.style === "dotted" ? chars.line.dottedVertical : chars.line.vertical;
1162
+ canvas.drawHorizontalLine(fromX, fromX + loopWidth, y, lineChar2);
1163
+ canvas.set(fromX + loopWidth, y, vChar);
1164
+ canvas.set(fromX + loopWidth, y + 1, vChar);
1165
+ canvas.drawHorizontalLine(fromX, fromX + loopWidth, y + 1, lineChar2);
1166
+ canvas.forceSet(fromX, y + 1, chars.arrow.left);
1167
+ if (message.text) {
1168
+ canvas.drawText(fromX + loopWidth + 2, y, message.text);
1169
+ }
1170
+ return;
1171
+ }
1172
+ const isLeftToRight = fromX < toX;
1173
+ const startX = isLeftToRight ? fromX + 1 : fromX - 1;
1174
+ const endX = isLeftToRight ? toX - 1 : toX + 1;
1175
+ const lineChar = message.style === "dotted" ? chars.line.dottedHorizontal : chars.line.horizontal;
1176
+ canvas.drawHorizontalLine(startX, endX, y, lineChar);
1177
+ const arrowChar = message.arrowStyle === "filled" ? isLeftToRight ? chars.arrow.right : chars.arrow.left : isLeftToRight ? chars.arrow.rightOpen : chars.arrow.leftOpen;
1178
+ canvas.forceSet(isLeftToRight ? toX : toX, y, arrowChar);
1179
+ if (message.text) {
1180
+ const textLen = message.text.length;
1181
+ const midX = Math.floor((fromX + toX) / 2);
1182
+ const textX = midX - Math.floor(textLen / 2);
1183
+ canvas.drawText(textX, y - 1, message.text);
1184
+ }
1185
+ }
1186
+ function renderSequence(ast, options) {
1187
+ if (ast.participants.size === 0) {
1188
+ return "";
1189
+ }
1190
+ const layout = layoutSequence(ast);
1191
+ const chars = getCharacterSet(options.charset);
1192
+ const canvas = new Canvas(layout.width + 4, layout.height + 4, options.charset);
1193
+ const topBoxY = options.paddingY;
1194
+ for (const pLayout of layout.participants) {
1195
+ drawParticipantBox(canvas, pLayout, topBoxY);
1196
+ }
1197
+ const lifelineStartY = topBoxY + PARTICIPANT_BOX_HEIGHT;
1198
+ const messagesEndY = lifelineStartY + ast.messages.length * MESSAGE_ROW_HEIGHT + LIFELINE_START_OFFSET;
1199
+ const bottomBoxY = messagesEndY + 1;
1200
+ for (const pLayout of layout.participants) {
1201
+ drawLifeline(canvas, pLayout, lifelineStartY, messagesEndY, chars);
1202
+ }
1203
+ for (const message of ast.messages) {
1204
+ const fromLayout = layout.participantMap.get(message.from);
1205
+ const toLayout = layout.participantMap.get(message.to);
1206
+ if (fromLayout && toLayout) {
1207
+ const messageY = lifelineStartY + LIFELINE_START_OFFSET + message.index * MESSAGE_ROW_HEIGHT;
1208
+ drawMessage(canvas, message, fromLayout, toLayout, messageY, chars);
1209
+ }
1210
+ }
1211
+ for (const pLayout of layout.participants) {
1212
+ drawParticipantBox(canvas, pLayout, bottomBoxY);
1213
+ }
1214
+ return canvas.toString();
1215
+ }
1216
+
1217
+ // src/index.ts
1218
+ var FLOWCHART_PATTERN = /^(?:graph|flowchart)\s+(LR|RL|TD|TB|BT)/im;
1219
+ var SEQUENCE_PATTERN = /^sequenceDiagram/im;
1220
+ var CLASS_PATTERN = /^classDiagram/im;
1221
+ var STATE_PATTERN = /^stateDiagram/im;
1222
+ var ER_PATTERN = /^erDiagram/im;
1223
+ function detectDiagramType(input) {
1224
+ const trimmed = input.trim();
1225
+ if (FLOWCHART_PATTERN.test(trimmed)) return "flowchart";
1226
+ if (SEQUENCE_PATTERN.test(trimmed)) return "sequence";
1227
+ if (CLASS_PATTERN.test(trimmed)) return "class";
1228
+ if (STATE_PATTERN.test(trimmed)) return "state";
1229
+ if (ER_PATTERN.test(trimmed)) return "er";
1230
+ return "unknown";
1231
+ }
1232
+ function render(input, options = {}) {
1233
+ const fullOptions = { ...DEFAULT_RENDER_OPTIONS, ...options };
1234
+ const diagramType = detectDiagramType(input);
1235
+ switch (diagramType) {
1236
+ case "flowchart": {
1237
+ const ast = parseFlowchart(input);
1238
+ return renderFlowchart(ast, fullOptions);
1239
+ }
1240
+ case "sequence": {
1241
+ const ast = parseSequence(input);
1242
+ return renderSequence(ast, fullOptions);
1243
+ }
1244
+ case "class":
1245
+ throw new Error("Class diagrams not yet implemented");
1246
+ case "state":
1247
+ throw new Error("State diagrams not yet implemented");
1248
+ case "er":
1249
+ throw new Error("ER diagrams not yet implemented");
1250
+ case "unknown":
1251
+ throw new Error(`Unknown diagram type. Input must start with a valid Mermaid diagram declaration (e.g., "graph LR", "sequenceDiagram", etc.)`);
1252
+ }
1253
+ }
1254
+ function renderToFlowchart(input, options = {}) {
1255
+ const fullOptions = { ...DEFAULT_RENDER_OPTIONS, ...options };
1256
+ const ast = parseFlowchart(input);
1257
+ return renderFlowchart(ast, fullOptions);
1258
+ }
1259
+ function renderToSequence(input, options = {}) {
1260
+ const fullOptions = { ...DEFAULT_RENDER_OPTIONS, ...options };
1261
+ const ast = parseSequence(input);
1262
+ return renderSequence(ast, fullOptions);
1263
+ }
1264
+
1265
+ exports.Canvas = Canvas;
1266
+ exports.DEFAULT_RENDER_OPTIONS = DEFAULT_RENDER_OPTIONS;
1267
+ exports.connectsDown = connectsDown;
1268
+ exports.connectsLeft = connectsLeft;
1269
+ exports.connectsRight = connectsRight;
1270
+ exports.connectsUp = connectsUp;
1271
+ exports.detectDiagramType = detectDiagramType;
1272
+ exports.getCharacterSet = getCharacterSet;
1273
+ exports.getJunctionChar = getJunctionChar;
1274
+ exports.isHorizontalChar = isHorizontalChar;
1275
+ exports.isJunctionChar = isJunctionChar;
1276
+ exports.isLineOrJunctionChar = isLineOrJunctionChar;
1277
+ exports.isVerticalChar = isVerticalChar;
1278
+ exports.layoutFlowchart = layoutFlowchart;
1279
+ exports.mergeJunctionChars = mergeJunctionChars;
1280
+ exports.parseFlowchart = parseFlowchart;
1281
+ exports.parseSequence = parseSequence;
1282
+ exports.render = render;
1283
+ exports.renderFlowchart = renderFlowchart;
1284
+ exports.renderSequence = renderSequence;
1285
+ exports.renderToFlowchart = renderToFlowchart;
1286
+ exports.renderToSequence = renderToSequence;
1287
+ //# sourceMappingURL=index.cjs.map
1288
+ //# sourceMappingURL=index.cjs.map