@shibayama/pdgkit 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/core.cjs ADDED
@@ -0,0 +1,2378 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/core/index.ts
21
+ var core_exports = {};
22
+ __export(core_exports, {
23
+ OP_TABLE: () => OP_TABLE,
24
+ PATTERN_LABEL: () => PATTERN_LABEL,
25
+ PATTERN_SOURCE: () => PATTERN_SOURCE,
26
+ SAMPLES: () => SAMPLES,
27
+ SAMPLE_ORDER: () => SAMPLE_ORDER,
28
+ chooseLabelPlacement: () => chooseLabelPlacement,
29
+ estimateTextWidth: () => estimateTextWidth,
30
+ layout: () => layout,
31
+ parse: () => parse,
32
+ refsToCsv: () => refsToCsv,
33
+ refsToMarkdown: () => refsToMarkdown,
34
+ render: () => render,
35
+ splitBilingual: () => splitBilingual,
36
+ stripComment: () => stripComment
37
+ });
38
+ module.exports = __toCommonJS(core_exports);
39
+
40
+ // src/core/parser.ts
41
+ var ID_PATTERN = /[A-Za-z0-9_*]+/.source;
42
+ var OP_TABLE = [
43
+ { lit: "<->", kind: "bidir" },
44
+ { lit: "=>", kind: "thick" },
45
+ { lit: "->", kind: "arrow" },
46
+ { lit: "<-", kind: "arrow", reverse: true },
47
+ { lit: ".>", kind: "dashed-arrow" },
48
+ { lit: "..", kind: "dashed" },
49
+ { lit: "-", kind: "line" }
50
+ ];
51
+ var DEF_RE = new RegExp(`^(${ID_PATTERN})\\s*=(?!>)\\s*(.*)$`);
52
+ var CONN_RE = new RegExp(
53
+ `^(${ID_PATTERN})\\s+(<->|=>|->|<-|\\.>|\\.\\.|-)\\s+(${ID_PATTERN})\\s*(?::\\s*(.*))?$`
54
+ );
55
+ var CONT_RE = new RegExp(`^(${ID_PATTERN})\\s*:\\s*(.+)$`);
56
+ function parse(source) {
57
+ const doc = {
58
+ nodes: /* @__PURE__ */ new Map(),
59
+ containments: [],
60
+ edges: [],
61
+ diagnostics: [],
62
+ kind: "block"
63
+ };
64
+ const lines = source.split(/\r?\n/);
65
+ for (let i = 0; i < lines.length; i++) {
66
+ const raw = stripComment(lines[i]);
67
+ const line = raw.trim();
68
+ if (!line) continue;
69
+ if (handleDef(line, i + 1, doc)) continue;
70
+ if (handleConn(line, i + 1, doc)) continue;
71
+ if (handleCont(line, i + 1, doc)) continue;
72
+ doc.diagnostics.push({
73
+ severity: "error",
74
+ line: i + 1,
75
+ col: 1,
76
+ message: `\u69CB\u6587\u4E0D\u660E: "${line}"`
77
+ });
78
+ }
79
+ doc.kind = inferKind(doc);
80
+ return doc;
81
+ }
82
+ function handleDef(line, lineNum, doc) {
83
+ const m = line.match(DEF_RE);
84
+ if (!m) return false;
85
+ const id = m[1];
86
+ const tail = m[2];
87
+ const label = splitBilingual(tail);
88
+ const existing = doc.nodes.get(id);
89
+ if (existing) {
90
+ doc.diagnostics.push({
91
+ severity: "warning",
92
+ line: lineNum,
93
+ col: 1,
94
+ message: `\u7B26\u53F7 "${id}" \u306F\u518D\u5B9A\u7FA9\u3055\u308C\u307E\u3057\u305F`
95
+ });
96
+ }
97
+ doc.nodes.set(id, {
98
+ id,
99
+ label,
100
+ implicit: false
101
+ });
102
+ return true;
103
+ }
104
+ function handleConn(line, lineNum, doc) {
105
+ const m = line.match(CONN_RE);
106
+ if (!m) return false;
107
+ const opLit = m[2];
108
+ const entry = OP_TABLE.find((o) => o.lit === opLit);
109
+ const from = entry.reverse ? m[3] : m[1];
110
+ const to = entry.reverse ? m[1] : m[3];
111
+ const labelText = m[4] ?? "";
112
+ const edge = {
113
+ from,
114
+ to,
115
+ op: entry.kind,
116
+ label: labelText.trim() ? splitBilingual(labelText) : void 0,
117
+ line: lineNum
118
+ };
119
+ doc.edges.push(edge);
120
+ ensureNode(doc, from);
121
+ ensureNode(doc, to);
122
+ return true;
123
+ }
124
+ function handleCont(line, lineNum, doc) {
125
+ const m = line.match(CONT_RE);
126
+ if (!m) return false;
127
+ const parent = m[1];
128
+ const rest = m[2].trim();
129
+ const children = rest.split(/\s+/).filter(Boolean);
130
+ const idRe = new RegExp(`^${ID_PATTERN}$`);
131
+ for (const c of children) {
132
+ if (!idRe.test(c)) {
133
+ doc.diagnostics.push({
134
+ severity: "error",
135
+ line: lineNum,
136
+ col: 1,
137
+ message: `\u5305\u542B\u306E\u5B50\u3068\u3057\u3066\u4E0D\u6B63\u306A\u30C8\u30FC\u30AF\u30F3: "${c}"`
138
+ });
139
+ return true;
140
+ }
141
+ }
142
+ doc.containments.push({ parent, children, line: lineNum });
143
+ ensureNode(doc, parent);
144
+ for (const c of children) ensureNode(doc, c);
145
+ return true;
146
+ }
147
+ function ensureNode(doc, id) {
148
+ let n = doc.nodes.get(id);
149
+ if (n) return n;
150
+ n = { id, label: {}, implicit: true };
151
+ doc.nodes.set(id, n);
152
+ return n;
153
+ }
154
+ function stripComment(line) {
155
+ let inQuote = false;
156
+ for (let i = 0; i < line.length; i++) {
157
+ const c = line[i];
158
+ if (c === '"') inQuote = !inQuote;
159
+ else if (!inQuote && c === "#") return line.slice(0, i);
160
+ }
161
+ return line;
162
+ }
163
+ function splitBilingual(text) {
164
+ const s = text.trim();
165
+ if (!s) return {};
166
+ const slashIdx = findBilingualSeparator(s);
167
+ if (slashIdx === -1) {
168
+ return { ja: stripQuotes(s) };
169
+ }
170
+ return {
171
+ ja: stripQuotes(s.slice(0, slashIdx).trim()),
172
+ en: stripQuotes(s.slice(slashIdx + 1).trim())
173
+ };
174
+ }
175
+ function findBilingualSeparator(s) {
176
+ let inQuote = false;
177
+ for (let i = 0; i < s.length; i++) {
178
+ if (s[i] === '"') {
179
+ inQuote = !inQuote;
180
+ continue;
181
+ }
182
+ if (!inQuote && s[i] === "/" && isSpace(s[i - 1]) && isSpace(s[i + 1])) return i;
183
+ }
184
+ return -1;
185
+ }
186
+ function isSpace(ch) {
187
+ return ch === " " || ch === " ";
188
+ }
189
+ function stripQuotes(s) {
190
+ if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
191
+ return s.slice(1, -1);
192
+ }
193
+ return s;
194
+ }
195
+ function inferKind(doc) {
196
+ if (doc.containments.length > 0) return "block";
197
+ for (const n of doc.nodes.values()) {
198
+ const ja = n.label.ja ?? "";
199
+ const en = n.label.en ?? "";
200
+ if (ja.endsWith("?") || en.endsWith("?")) return "flow";
201
+ }
202
+ if (doc.nodes.has("*")) return "state";
203
+ const pairs = /* @__PURE__ */ new Set();
204
+ for (const e of doc.edges) {
205
+ if (e.op === "bidir") return "seq";
206
+ const fwd = `${e.from}|${e.to}`;
207
+ const rev = `${e.to}|${e.from}`;
208
+ if (pairs.has(rev)) return "seq";
209
+ pairs.add(fwd);
210
+ }
211
+ return "flow";
212
+ }
213
+
214
+ // src/core/layout.ts
215
+ var NODE_W = 36;
216
+ var NODE_H = 14;
217
+ var PAD = 8;
218
+ var GRID_GAP = 24;
219
+ var TITLE_H = 5;
220
+ var PARENT_EDGE_LANE_H = 14;
221
+ var MARGIN = 8;
222
+ var ROOT_GAP = MARGIN * 4;
223
+ var ROUTE_GAP = PAD * 1.5;
224
+ var OBSTACLE_PAD = 0.8;
225
+ var BORDER_CLEARANCE = 4;
226
+ var ARROW_TERMINAL_CLEARANCE = 4.6;
227
+ var THICK_ARROW_TERMINAL_CLEARANCE = 5.4;
228
+ var VERTICAL_PORT_RATIO = 0.25;
229
+ var PORT_STUB = 6;
230
+ var MAX_ROUTE_LANES = 18;
231
+ var EPS = 1e-3;
232
+ function layout(doc) {
233
+ switch (doc.kind) {
234
+ case "block":
235
+ return layoutBlock(doc);
236
+ case "flow":
237
+ return layoutFlow(doc);
238
+ case "state":
239
+ return layoutState(doc);
240
+ case "seq":
241
+ return layoutSeq(doc);
242
+ }
243
+ }
244
+ function layoutBlock(doc) {
245
+ const childMap = /* @__PURE__ */ new Map();
246
+ for (const c of doc.containments) childMap.set(c.parent, c.children);
247
+ const parentMap = /* @__PURE__ */ new Map();
248
+ for (const c of doc.containments) {
249
+ for (const child of c.children) parentMap.set(child, c.parent);
250
+ }
251
+ const childIds = /* @__PURE__ */ new Set();
252
+ for (const cs of childMap.values()) for (const c of cs) childIds.add(c);
253
+ const allIds = [...doc.nodes.keys()];
254
+ const roots = allIds.filter((id) => !childIds.has(id));
255
+ const placed = [];
256
+ const positions = /* @__PURE__ */ new Map();
257
+ function size(id) {
258
+ const children = childMap.get(id);
259
+ if (!children || children.length === 0) return { w: NODE_W, h: NODE_H };
260
+ const sizes = children.map(size);
261
+ if (arrangementOf(id) === "grid") {
262
+ const cols = gridColsOf(id, children);
263
+ const colW = Math.max(...sizes.map((s) => s.w));
264
+ const rowHeights = gridRowHeights(sizes, cols);
265
+ return {
266
+ w: cols * colW + 2 * PAD + (cols - 1) * GRID_GAP,
267
+ h: titleHeightOf(id) + rowHeights.reduce((sum, h) => sum + h, 0) + 2 * PAD + Math.max(0, rowHeights.length - 1) * GRID_GAP
268
+ };
269
+ }
270
+ const maxW = Math.max(...sizes.map((s) => s.w));
271
+ const totH = sizes.reduce((a, b) => a + b.h, 0);
272
+ return {
273
+ w: maxW + 2 * PAD,
274
+ h: titleHeightOf(id) + totH + 2 * PAD + (children.length - 1) * GRID_GAP
275
+ };
276
+ }
277
+ function arrangementOf(id) {
278
+ const children = childMap.get(id);
279
+ if (!children || children.length <= 2) return "stack";
280
+ if (hasParentToChildEdges(id, children)) return "grid";
281
+ return hasLinearChildFlow(children, doc.edges, childMap) ? "stack" : "grid";
282
+ }
283
+ function gridColsOf(id, children) {
284
+ if (hasParentToChildEdges(id, children)) return Math.min(children.length, 4);
285
+ if (children.length >= 7) return 4;
286
+ if (children.length >= 5) return 3;
287
+ return 2;
288
+ }
289
+ function gridRowHeights(sizes, cols) {
290
+ const rows = Math.ceil(sizes.length / cols);
291
+ const heights = [];
292
+ for (let row = 0; row < rows; row++) {
293
+ const rowSizes = sizes.slice(row * cols, row * cols + cols);
294
+ heights.push(Math.max(...rowSizes.map((s) => s.h)));
295
+ }
296
+ return heights;
297
+ }
298
+ function titleHeightOf(id) {
299
+ const children = childMap.get(id);
300
+ return children && hasParentToChildEdges(id, children) ? TITLE_H + PARENT_EDGE_LANE_H : TITLE_H;
301
+ }
302
+ function hasParentToChildEdges(id, children) {
303
+ return doc.edges.some((edge) => edge.from === id && children.some((child) => child === edge.to || containsDescendant(child, edge.to, childMap)));
304
+ }
305
+ function place(id, ox, oy) {
306
+ const s = size(id);
307
+ positions.set(id, { x: ox, y: oy, w: s.w, h: s.h });
308
+ const node = doc.nodes.get(id);
309
+ const children = childMap.get(id);
310
+ if (!children || children.length === 0) {
311
+ placed.push({
312
+ id,
313
+ x: ox,
314
+ y: oy,
315
+ w: s.w,
316
+ h: s.h,
317
+ label: node?.label ?? {},
318
+ shape: "box",
319
+ isContainer: false
320
+ });
321
+ return;
322
+ }
323
+ const sizes = children.map(size);
324
+ if (arrangementOf(id) === "grid") {
325
+ const cols = gridColsOf(id, children);
326
+ const colW = Math.max(...sizes.map((s2) => s2.w));
327
+ const rowHeights = gridRowHeights(sizes, cols);
328
+ const rowTops = [];
329
+ let rowTop = oy + titleHeightOf(id) + PAD;
330
+ for (let row = 0; row < rowHeights.length; row++) {
331
+ rowTops[row] = rowTop;
332
+ rowTop += rowHeights[row] + GRID_GAP;
333
+ }
334
+ for (let i = 0; i < children.length; i++) {
335
+ const r = Math.floor(i / cols);
336
+ const cc = i % cols;
337
+ const cx = ox + PAD + cc * (colW + GRID_GAP) + (colW - sizes[i].w) / 2;
338
+ const cy = rowTops[r] + (rowHeights[r] - sizes[i].h) / 2;
339
+ place(children[i], cx, cy);
340
+ }
341
+ alignGridRows(children, sizes, rowHeights, rowTops, cols, childMap, doc.edges, positions, placed);
342
+ } else {
343
+ const maxW = Math.max(...sizes.map((s2) => s2.w));
344
+ let yy = oy + titleHeightOf(id) + PAD;
345
+ for (let i = 0; i < children.length; i++) {
346
+ const cx = ox + PAD + (maxW - sizes[i].w) / 2;
347
+ place(children[i], cx, yy);
348
+ yy += sizes[i].h + GRID_GAP;
349
+ }
350
+ }
351
+ placed.push({
352
+ id,
353
+ x: ox,
354
+ y: oy,
355
+ w: s.w,
356
+ h: s.h,
357
+ label: node?.label ?? {},
358
+ shape: "box",
359
+ isContainer: true
360
+ });
361
+ }
362
+ let cur = MARGIN;
363
+ for (const r of roots) {
364
+ const previousIds = new Set(positions.keys());
365
+ place(r, cur, MARGIN);
366
+ const subtreeIds = collectSubtreeIds(r, childMap);
367
+ const dy = rootAlignmentDelta(r, subtreeIds, previousIds, doc.edges, positions);
368
+ if (Math.abs(dy) >= EPS) shiftSubtree(subtreeIds, dy, positions, placed);
369
+ const sz = size(r);
370
+ cur += sz.w + ROOT_GAP;
371
+ }
372
+ const edges = makeBlockEdges(doc.edges, positions, placed, parentMap);
373
+ placed.sort((a, b) => {
374
+ if (a.isContainer !== b.isContainer) return a.isContainer ? -1 : 1;
375
+ if (a.isContainer && b.isContainer) {
376
+ const depthDiff = depthOf(a.id, parentMap) - depthOf(b.id, parentMap);
377
+ if (depthDiff !== 0) return depthDiff;
378
+ return b.w * b.h - a.w * a.h;
379
+ }
380
+ return a.y - b.y || a.x - b.x;
381
+ });
382
+ const boxes = [...positions.values()];
383
+ const edgePoints = edges.flatMap((edge) => edge.points);
384
+ const maxBoxX = boxes.length ? Math.max(...boxes.map((b) => b.x + b.w)) : MARGIN;
385
+ const maxBoxY = boxes.length ? Math.max(...boxes.map((b) => b.y + b.h)) : MARGIN;
386
+ const maxEdgeX = edgePoints.length ? Math.max(...edgePoints.map(([x]) => x)) : MARGIN;
387
+ const maxEdgeY = edgePoints.length ? Math.max(...edgePoints.map(([, y]) => y)) : MARGIN;
388
+ const width = Math.max(maxBoxX, maxEdgeX) + MARGIN;
389
+ const height = Math.max(maxBoxY, maxEdgeY) + MARGIN;
390
+ return { nodes: placed, edges, width, height, kind: "block" };
391
+ }
392
+ function layoutFlow(doc) {
393
+ const ids = [...doc.nodes.keys()];
394
+ const { byRank } = computeRanks(doc);
395
+ function shapeOf(id) {
396
+ const n = doc.nodes.get(id);
397
+ const ja = n?.label.ja ?? "";
398
+ const en = n?.label.en ?? "";
399
+ if (ja.endsWith("?") || en.endsWith("?")) return "diamond";
400
+ const inc = doc.edges.filter((e) => e.to === id).length;
401
+ const out = doc.edges.filter((e) => e.from === id).length;
402
+ if (inc === 0 || out === 0) return "round";
403
+ return "box";
404
+ }
405
+ const V_GAP = 14;
406
+ const H_GAP = 10;
407
+ const positions = /* @__PURE__ */ new Map();
408
+ const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
409
+ let y = MARGIN;
410
+ let maxX = 0;
411
+ for (const r of sortedRanks) {
412
+ const lane = byRank.get(r);
413
+ const widths = lane.map((id) => shapeOf(id) === "diamond" ? NODE_W * 1.2 : NODE_W);
414
+ const totalW = widths.reduce((a, b) => a + b, 0) + (lane.length - 1) * H_GAP;
415
+ let x = MARGIN;
416
+ const canvasW = Math.max(totalW + 2 * MARGIN, 200);
417
+ x = (canvasW - totalW) / 2;
418
+ for (let i = 0; i < lane.length; i++) {
419
+ positions.set(lane[i], { x, y, w: widths[i], h: NODE_H });
420
+ x += widths[i] + H_GAP;
421
+ }
422
+ if (x > maxX) maxX = x;
423
+ y += NODE_H + V_GAP;
424
+ }
425
+ for (const id of ids) {
426
+ if (!positions.has(id)) {
427
+ positions.set(id, { x: MARGIN, y, w: NODE_W, h: NODE_H });
428
+ y += NODE_H + V_GAP;
429
+ }
430
+ }
431
+ const placed = [];
432
+ for (const [id, b] of positions) {
433
+ placed.push({
434
+ id,
435
+ ...b,
436
+ label: doc.nodes.get(id)?.label ?? {},
437
+ shape: shapeOf(id),
438
+ isContainer: false
439
+ });
440
+ }
441
+ const edges = makeEdges(doc.edges, positions);
442
+ return { nodes: placed, edges, width: maxX + MARGIN, height: y + MARGIN, kind: "flow" };
443
+ }
444
+ function layoutState(doc) {
445
+ const { byRank } = computeRanks(doc);
446
+ function shapeOf(id) {
447
+ if (id === "*") return "circle";
448
+ return "round";
449
+ }
450
+ const V_GAP = 14, H_GAP = 10;
451
+ const positions = /* @__PURE__ */ new Map();
452
+ const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
453
+ let y = MARGIN;
454
+ let maxX = 0;
455
+ for (const r of sortedRanks) {
456
+ const lane = byRank.get(r);
457
+ const widths = lane.map((id) => shapeOf(id) === "circle" ? 6 : NODE_W);
458
+ const heights = lane.map((id) => shapeOf(id) === "circle" ? 6 : NODE_H);
459
+ const totalW = widths.reduce((a, b) => a + b, 0) + (lane.length - 1) * H_GAP;
460
+ const canvasW = Math.max(totalW + 2 * MARGIN, 200);
461
+ let x = (canvasW - totalW) / 2;
462
+ for (let i = 0; i < lane.length; i++) {
463
+ positions.set(lane[i], { x, y: y + (NODE_H - heights[i]) / 2, w: widths[i], h: heights[i] });
464
+ x += widths[i] + H_GAP;
465
+ }
466
+ if (x > maxX) maxX = x;
467
+ y += NODE_H + V_GAP;
468
+ }
469
+ for (const id of doc.nodes.keys()) {
470
+ if (!positions.has(id)) {
471
+ positions.set(id, { x: MARGIN, y, w: NODE_W, h: NODE_H });
472
+ y += NODE_H + V_GAP;
473
+ }
474
+ }
475
+ const placed = [];
476
+ for (const [id, b] of positions) {
477
+ placed.push({
478
+ id,
479
+ ...b,
480
+ label: doc.nodes.get(id)?.label ?? {},
481
+ shape: shapeOf(id),
482
+ isContainer: false
483
+ });
484
+ }
485
+ const edges = makeEdges(doc.edges, positions);
486
+ return { nodes: placed, edges, width: maxX + MARGIN, height: y + MARGIN, kind: "state" };
487
+ }
488
+ function layoutSeq(doc) {
489
+ const seen = /* @__PURE__ */ new Set();
490
+ const actors = [];
491
+ for (const e of doc.edges) {
492
+ for (const id of [e.from, e.to]) {
493
+ if (!seen.has(id)) {
494
+ seen.add(id);
495
+ actors.push(id);
496
+ }
497
+ }
498
+ }
499
+ for (const id of doc.nodes.keys()) {
500
+ if (!seen.has(id)) {
501
+ seen.add(id);
502
+ actors.push(id);
503
+ }
504
+ }
505
+ const ACTOR_W = 40, ACTOR_H = 12, COL_GAP = 28, MSG_GAP = 12;
506
+ const xOf = /* @__PURE__ */ new Map();
507
+ const placed = [];
508
+ let x = MARGIN;
509
+ for (const id of actors) {
510
+ xOf.set(id, x + ACTOR_W / 2);
511
+ placed.push({
512
+ id,
513
+ x,
514
+ y: MARGIN,
515
+ w: ACTOR_W,
516
+ h: ACTOR_H,
517
+ label: doc.nodes.get(id)?.label ?? {},
518
+ shape: "actor",
519
+ isContainer: false
520
+ });
521
+ x += ACTOR_W + COL_GAP;
522
+ }
523
+ let y = MARGIN + ACTOR_H + MSG_GAP;
524
+ const msgEdges = [];
525
+ for (const e of doc.edges) {
526
+ const xa = xOf.get(e.from);
527
+ const xb = xOf.get(e.to);
528
+ if (xa === void 0 || xb === void 0) continue;
529
+ msgEdges.push({
530
+ from: e.from,
531
+ to: e.to,
532
+ points: [[xa, y], [xb, y]],
533
+ label: e.label,
534
+ op: e.op
535
+ });
536
+ y += MSG_GAP;
537
+ }
538
+ const lifelines = actors.map((id) => ({
539
+ from: id,
540
+ to: id,
541
+ points: [[xOf.get(id), MARGIN + ACTOR_H], [xOf.get(id), y + MSG_GAP]],
542
+ op: "dashed",
543
+ isLifeline: true
544
+ }));
545
+ return {
546
+ nodes: placed,
547
+ edges: [...lifelines, ...msgEdges],
548
+ width: x,
549
+ height: y + MARGIN * 2,
550
+ kind: "seq"
551
+ };
552
+ }
553
+ function computeRanks(doc) {
554
+ const ids = [...doc.nodes.keys()];
555
+ const incoming = /* @__PURE__ */ new Map();
556
+ for (const id of ids) incoming.set(id, 0);
557
+ for (const e of doc.edges) incoming.set(e.to, (incoming.get(e.to) ?? 0) + 1);
558
+ const outgoing = /* @__PURE__ */ new Map();
559
+ for (const e of doc.edges) {
560
+ if (!outgoing.has(e.from)) outgoing.set(e.from, []);
561
+ outgoing.get(e.from).push(e.to);
562
+ }
563
+ let sources = ids.filter((id) => incoming.get(id) === 0);
564
+ if (sources.length === 0) {
565
+ sources = ids.includes("*") ? ["*"] : ids.length ? [ids[0]] : [];
566
+ }
567
+ const rank = /* @__PURE__ */ new Map();
568
+ const visited = /* @__PURE__ */ new Set();
569
+ for (const s of sources) {
570
+ rank.set(s, 0);
571
+ visited.add(s);
572
+ }
573
+ let frontier = [...sources];
574
+ while (frontier.length) {
575
+ const next = [];
576
+ for (const n of frontier) {
577
+ const r = rank.get(n);
578
+ for (const m of outgoing.get(n) ?? []) {
579
+ if (!visited.has(m)) {
580
+ rank.set(m, r + 1);
581
+ visited.add(m);
582
+ next.push(m);
583
+ }
584
+ }
585
+ }
586
+ frontier = next;
587
+ }
588
+ let maxRank = 0;
589
+ for (const r of rank.values()) if (r > maxRank) maxRank = r;
590
+ for (const id of ids) {
591
+ if (!visited.has(id)) {
592
+ maxRank++;
593
+ rank.set(id, maxRank);
594
+ visited.add(id);
595
+ }
596
+ }
597
+ const byRank = /* @__PURE__ */ new Map();
598
+ for (const [id, r] of rank) {
599
+ if (!byRank.has(r)) byRank.set(r, []);
600
+ byRank.get(r).push(id);
601
+ }
602
+ return { byRank };
603
+ }
604
+ function makeEdges(srcEdges, positions) {
605
+ return srcEdges.map((e) => {
606
+ const a = positions.get(e.from);
607
+ const b = positions.get(e.to);
608
+ if (!a || !b) {
609
+ return { from: e.from, to: e.to, points: [], label: e.label, op: e.op };
610
+ }
611
+ return {
612
+ from: e.from,
613
+ to: e.to,
614
+ points: orthogonalRoute(a, b),
615
+ label: e.label,
616
+ op: e.op
617
+ };
618
+ });
619
+ }
620
+ function hasLinearChildFlow(children, edges, childMap) {
621
+ const childSet = new Set(children);
622
+ const pairs = /* @__PURE__ */ new Set();
623
+ const incoming = /* @__PURE__ */ new Map();
624
+ const outgoing = /* @__PURE__ */ new Map();
625
+ for (const child of children) {
626
+ incoming.set(child, 0);
627
+ outgoing.set(child, 0);
628
+ }
629
+ for (const edge of edges) {
630
+ const from = topChildFor(edge.from, childSet, childMap);
631
+ const to = topChildFor(edge.to, childSet, childMap);
632
+ if (!from || !to || from === to) continue;
633
+ const key = `${from}|${to}`;
634
+ if (pairs.has(key)) continue;
635
+ pairs.add(key);
636
+ outgoing.set(from, (outgoing.get(from) ?? 0) + 1);
637
+ incoming.set(to, (incoming.get(to) ?? 0) + 1);
638
+ }
639
+ if (pairs.size < children.length - 1) return false;
640
+ let starts = 0;
641
+ let ends = 0;
642
+ for (const child of children) {
643
+ const inc = incoming.get(child) ?? 0;
644
+ const out = outgoing.get(child) ?? 0;
645
+ if (inc > 1 || out > 1) return false;
646
+ if (inc === 0 && out === 1) starts++;
647
+ if (inc === 1 && out === 0) ends++;
648
+ }
649
+ return starts === 1 && ends === 1;
650
+ }
651
+ function topChildFor(id, childSet, childMap) {
652
+ if (childSet.has(id)) return id;
653
+ for (const child of childSet) {
654
+ if (containsDescendant(child, id, childMap)) return child;
655
+ }
656
+ return void 0;
657
+ }
658
+ function containsDescendant(ancestor, id, childMap) {
659
+ const children = childMap.get(ancestor);
660
+ if (!children) return false;
661
+ for (const child of children) {
662
+ if (child === id || containsDescendant(child, id, childMap)) return true;
663
+ }
664
+ return false;
665
+ }
666
+ function collectSubtreeIds(id, childMap) {
667
+ const ids = /* @__PURE__ */ new Set([id]);
668
+ for (const child of childMap.get(id) ?? []) {
669
+ for (const descendant of collectSubtreeIds(child, childMap)) ids.add(descendant);
670
+ }
671
+ return ids;
672
+ }
673
+ function alignGridRows(children, sizes, rowHeights, rowTops, cols, childMap, edges, positions, placed) {
674
+ const rows = Math.ceil(children.length / cols);
675
+ for (let row = 0; row < rows; row++) {
676
+ const rowChildren = children.slice(row * cols, row * cols + cols);
677
+ const rowTop = rowTops[row];
678
+ const rowH = rowHeights[row];
679
+ if (rowTop === void 0 || rowH === void 0) continue;
680
+ for (const child of rowChildren) {
681
+ const childIndex = children.indexOf(child);
682
+ const childSize = sizes[childIndex];
683
+ if (!childSize || childSize.h >= rowH - EPS) continue;
684
+ const subtreeIds = collectSubtreeIds(child, childMap);
685
+ const siblingIds = /* @__PURE__ */ new Set();
686
+ for (const sibling of rowChildren) {
687
+ if (sibling === child) continue;
688
+ for (const id of collectSubtreeIds(sibling, childMap)) siblingIds.add(id);
689
+ }
690
+ const deltas = [];
691
+ for (const edge of edges) {
692
+ const fromChild = subtreeIds.has(edge.from);
693
+ const toChild = subtreeIds.has(edge.to);
694
+ if (fromChild && siblingIds.has(edge.to)) {
695
+ addWeightedDelta(
696
+ deltas,
697
+ centerY(positions.get(edge.to)) - centerY(positions.get(edge.from)),
698
+ alignmentWeight(edge, false)
699
+ );
700
+ } else if (toChild && siblingIds.has(edge.from)) {
701
+ addWeightedDelta(
702
+ deltas,
703
+ centerY(positions.get(edge.from)) - centerY(positions.get(edge.to)),
704
+ alignmentWeight(edge, true)
705
+ );
706
+ }
707
+ }
708
+ if (deltas.length === 0) continue;
709
+ deltas.sort((a, b) => a - b);
710
+ const desired = deltas[Math.floor(deltas.length / 2)];
711
+ const box = positions.get(child);
712
+ if (!box) continue;
713
+ const minDy = rowTop - box.y;
714
+ const maxDy = rowTop + rowH - childSize.h - box.y;
715
+ const dy = Math.min(maxDy, Math.max(minDy, desired));
716
+ if (Math.abs(dy) >= EPS) shiftSubtree(subtreeIds, dy, positions, placed);
717
+ }
718
+ }
719
+ }
720
+ function rootAlignmentDelta(rootId, subtreeIds, previousIds, edges, positions) {
721
+ const deltas = [];
722
+ for (const edge of edges) {
723
+ const fromCurrent = subtreeIds.has(edge.from);
724
+ const toCurrent = subtreeIds.has(edge.to);
725
+ const fromPrevious = previousIds.has(edge.from);
726
+ const toPrevious = previousIds.has(edge.to);
727
+ if (fromCurrent && toPrevious) {
728
+ addWeightedDelta(
729
+ deltas,
730
+ centerY(positions.get(edge.to)) - centerY(positions.get(edge.from)),
731
+ alignmentWeight(edge, false)
732
+ );
733
+ } else if (toCurrent && fromPrevious) {
734
+ addWeightedDelta(
735
+ deltas,
736
+ centerY(positions.get(edge.from)) - centerY(positions.get(edge.to)),
737
+ alignmentWeight(edge, true)
738
+ );
739
+ }
740
+ }
741
+ if (deltas.length === 0) return 0;
742
+ deltas.sort((a, b) => a - b);
743
+ const desired = deltas[Math.floor(deltas.length / 2)];
744
+ const root = positions.get(rootId);
745
+ if (!root) return desired;
746
+ return Math.max(MARGIN - root.y, desired);
747
+ }
748
+ function addWeightedDelta(deltas, delta, weight) {
749
+ if (!Number.isFinite(delta)) return;
750
+ for (let i = 0; i < weight; i++) deltas.push(delta);
751
+ }
752
+ function alignmentWeight(edge, currentIsTarget) {
753
+ const feedback = edge.op === "dashed" || edge.op === "dashed-arrow";
754
+ return (feedback ? 1 : 3) + (currentIsTarget ? 1 : 0);
755
+ }
756
+ function centerY(box) {
757
+ return box ? box.y + box.h / 2 : Number.NaN;
758
+ }
759
+ function shiftSubtree(ids, dy, positions, placed) {
760
+ for (const id of ids) {
761
+ const box = positions.get(id);
762
+ if (box) box.y += dy;
763
+ }
764
+ for (const node of placed) {
765
+ if (ids.has(node.id)) node.y += dy;
766
+ }
767
+ }
768
+ function makeBlockEdges(srcEdges, positions, obstacles, parentMap) {
769
+ const containerIds = new Set(obstacles.filter((o) => o.isContainer).map((o) => o.id));
770
+ const plans = srcEdges.map((edge, index) => {
771
+ const a = positions.get(edge.from);
772
+ const b = positions.get(edge.to);
773
+ if (!a || !b) {
774
+ return { edge, index, endpointBoundaries: [], endpointInteriorBarriers: [] };
775
+ }
776
+ const routeA = routeEndpointBox(edge.from, edge.to, positions, parentMap, containerIds) ?? a;
777
+ const routeB = routeEndpointBox(edge.to, edge.from, positions, parentMap, containerIds) ?? b;
778
+ const endpointBoundaries = [
779
+ ...routeA === a ? [] : [a],
780
+ ...routeB === b ? [] : [b]
781
+ ];
782
+ const endpointInteriorBarriers = [
783
+ ...isExternalContainerEndpoint(edge.from, edge.to, parentMap, containerIds) ? [a] : [],
784
+ ...isExternalContainerEndpoint(edge.to, edge.from, parentMap, containerIds) ? [b] : []
785
+ ];
786
+ return {
787
+ edge,
788
+ index,
789
+ routeA,
790
+ routeB,
791
+ endpointBoundaries,
792
+ endpointInteriorBarriers,
793
+ bounds: commonRoutingBounds(edge.from, edge.to, parentMap, positions)
794
+ };
795
+ });
796
+ return routePlansSequentially(plans, obstacles, parentMap);
797
+ }
798
+ function routePlansSequentially(plans, baseObstacles, parentMap) {
799
+ const routed = [];
800
+ for (const plan of plans) {
801
+ const searchBox = routeSearchBox(plan);
802
+ const routedArrowGuards = routed.flatMap((edge, index) => arrowGuardObstaclesFor(edge, index));
803
+ const routedEdgeGuards = routed.flatMap((edge, index) => edgeGuardObstaclesFor(edge, index));
804
+ const extraArrowGuards = routedArrowGuards.filter((guard) => guard.edgeIndex !== plan.index && !sharesRouteEndpoint(guard, plan.edge) && (!searchBox || boxesOverlap(searchBox, guard)));
805
+ const extraEdgeGuards = routedEdgeGuards.filter((guard) => shouldUseEdgeGuard(guard, plan, searchBox));
806
+ routed.push(routeBlockPlan(plan, [...baseObstacles, ...extraArrowGuards, ...extraEdgeGuards], parentMap));
807
+ }
808
+ return routed;
809
+ }
810
+ function routeBlockPlan(plan, obstacles, parentMap) {
811
+ const { edge, routeA, routeB } = plan;
812
+ if (!routeA || !routeB) {
813
+ return { from: edge.from, to: edge.to, points: [], label: edge.label, op: edge.op };
814
+ }
815
+ return {
816
+ from: edge.from,
817
+ to: edge.to,
818
+ points: avoidObstaclesRoute(
819
+ routeA,
820
+ routeB,
821
+ edge.from,
822
+ edge.to,
823
+ obstacles,
824
+ parentMap,
825
+ plan.bounds,
826
+ plan.endpointBoundaries,
827
+ plan.endpointInteriorBarriers,
828
+ edge.op
829
+ ),
830
+ label: edge.label,
831
+ op: edge.op
832
+ };
833
+ }
834
+ function arrowGuardObstaclesFor(edge, edgeIndex) {
835
+ if (edge.isLifeline || edge.points.length < 2) return [];
836
+ const guards = [];
837
+ if (hasEndArrow(edge.op)) {
838
+ guards.push(makeArrowGuard(edge, edgeIndex, edge.points[edge.points.length - 1], "end"));
839
+ }
840
+ if (edge.op === "bidir") {
841
+ guards.push(makeArrowGuard(edge, edgeIndex, edge.points[0], "start"));
842
+ }
843
+ return guards;
844
+ }
845
+ function hasEndArrow(op) {
846
+ return op !== "line" && op !== "dashed";
847
+ }
848
+ function makeArrowGuard(edge, edgeIndex, tip, side) {
849
+ const half = edge.op === "thick" ? 5 : 4.2;
850
+ return {
851
+ id: `__arrow_guard_${edgeIndex}_${side}`,
852
+ x: tip[0] - half,
853
+ y: tip[1] - half,
854
+ w: half * 2,
855
+ h: half * 2,
856
+ isContainer: false,
857
+ edgeIndex,
858
+ edgeFrom: edge.from,
859
+ edgeTo: edge.to
860
+ };
861
+ }
862
+ function sharesRouteEndpoint(guard, edge) {
863
+ return guard.edgeFrom === edge.from || guard.edgeFrom === edge.to || guard.edgeTo === edge.from || guard.edgeTo === edge.to;
864
+ }
865
+ function edgeGuardObstaclesFor(edge, edgeIndex) {
866
+ if (edge.points.length < 2) return [];
867
+ const guards = [];
868
+ const clear = 2.2;
869
+ const trim = 1.2;
870
+ for (let i = 0; i < edge.points.length - 1; i++) {
871
+ const a = edge.points[i];
872
+ const b = edge.points[i + 1];
873
+ const len = segmentLength(a, b);
874
+ if (len <= trim * 2) continue;
875
+ if (Math.abs(a[0] - b[0]) < EPS) {
876
+ const y1 = Math.min(a[1], b[1]) + trim;
877
+ const y2 = Math.max(a[1], b[1]) - trim;
878
+ guards.push({
879
+ id: `__edge_guard_${edgeIndex}_${i}`,
880
+ x: a[0] - clear,
881
+ y: y1,
882
+ w: clear * 2,
883
+ h: y2 - y1,
884
+ isContainer: false,
885
+ edgeIndex,
886
+ edgeOp: edge.op,
887
+ orientation: "vertical"
888
+ });
889
+ } else if (Math.abs(a[1] - b[1]) < EPS) {
890
+ const x1 = Math.min(a[0], b[0]) + trim;
891
+ const x2 = Math.max(a[0], b[0]) - trim;
892
+ guards.push({
893
+ id: `__edge_guard_${edgeIndex}_${i}`,
894
+ x: x1,
895
+ y: a[1] - clear,
896
+ w: x2 - x1,
897
+ h: clear * 2,
898
+ isContainer: false,
899
+ edgeIndex,
900
+ edgeOp: edge.op,
901
+ orientation: "horizontal"
902
+ });
903
+ }
904
+ }
905
+ return guards;
906
+ }
907
+ function shouldUseEdgeGuard(guard, plan, searchBox) {
908
+ if (guard.edgeIndex === plan.index) return false;
909
+ return !searchBox || boxesOverlap(searchBox, guard);
910
+ }
911
+ function routeSearchBox(plan) {
912
+ if (!plan.routeA || !plan.routeB) return void 0;
913
+ const left = Math.min(plan.routeA.x, plan.routeB.x);
914
+ const top = Math.min(plan.routeA.y, plan.routeB.y);
915
+ const right = Math.max(plan.routeA.x + plan.routeA.w, plan.routeB.x + plan.routeB.w);
916
+ const bottom = Math.max(plan.routeA.y + plan.routeA.h, plan.routeB.y + plan.routeB.h);
917
+ const pad = ROUTE_GAP * 4;
918
+ const candidate = {
919
+ x: left - pad,
920
+ y: top - pad,
921
+ w: right - left + pad * 2,
922
+ h: bottom - top + pad * 2
923
+ };
924
+ if (!plan.bounds) return candidate;
925
+ return {
926
+ x: Math.max(candidate.x, plan.bounds.x),
927
+ y: Math.max(candidate.y, plan.bounds.y),
928
+ w: Math.min(candidate.x + candidate.w, plan.bounds.x + plan.bounds.w) - Math.max(candidate.x, plan.bounds.x),
929
+ h: Math.min(candidate.y + candidate.h, plan.bounds.y + plan.bounds.h) - Math.max(candidate.y, plan.bounds.y)
930
+ };
931
+ }
932
+ function boxesOverlap(a, b) {
933
+ return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
934
+ }
935
+ function routeEndpointBox(id, otherId, positions, parentMap, containerIds) {
936
+ const container = positions.get(id);
937
+ const other = positions.get(otherId);
938
+ if (!container || !other) return void 0;
939
+ if (!isAncestorOf(id, otherId, parentMap)) {
940
+ if (!containerIds.has(id)) return void 0;
941
+ return container;
942
+ }
943
+ const left = container.x + PAD;
944
+ const right = container.x + container.w - PAD;
945
+ const top = container.y + TITLE_H + PAD / 2;
946
+ const x = Math.min(right, Math.max(left, other.x + other.w / 2));
947
+ return { x: x - 0.1, y: top - 0.1, w: 0.2, h: 0.2 };
948
+ }
949
+ function isExternalContainerEndpoint(id, otherId, parentMap, containerIds) {
950
+ return containerIds.has(id) && !isAncestorOf(id, otherId, parentMap);
951
+ }
952
+ function isAncestorOf(ancestor, id, parentMap) {
953
+ let cur = id;
954
+ const seen = /* @__PURE__ */ new Set();
955
+ while (parentMap.has(cur) && !seen.has(cur)) {
956
+ seen.add(cur);
957
+ cur = parentMap.get(cur);
958
+ if (cur === ancestor) return true;
959
+ }
960
+ return false;
961
+ }
962
+ function depthOf(id, parentMap) {
963
+ let depth = 0;
964
+ let cur = id;
965
+ const seen = /* @__PURE__ */ new Set();
966
+ while (parentMap.has(cur) && !seen.has(cur)) {
967
+ seen.add(cur);
968
+ cur = parentMap.get(cur);
969
+ depth++;
970
+ }
971
+ return depth;
972
+ }
973
+ function avoidObstaclesRoute(a, b, from, to, obstacles, parentMap, bounds, endpointBoundaries = [], endpointInteriorBarriers = [], op = "line", strictEdgeGuards = true) {
974
+ const routedObstacles = obstacles.filter((o) => o.id !== from && o.id !== to);
975
+ const leafObstacles = routedObstacles.filter((o) => !o.isContainer);
976
+ const hardLeafObstacles = leafObstacles.filter((o) => o.id.startsWith("__arrow_guard_"));
977
+ const allowedContainers = allowedContainerIds(from, to, parentMap);
978
+ const passableContainerObstacles = routedObstacles.filter((o) => o.isContainer && allowedContainers.has(o.id));
979
+ const blockedContainerObstacles = routedObstacles.filter((o) => o.isContainer && !allowedContainers.has(o.id));
980
+ const externalContainerObstacles = passableContainerObstacles.filter((o) => isAncestorOf(o.id, from, parentMap) !== isAncestorOf(o.id, to, parentMap));
981
+ const boundaryObstacles = endpointBoundaries.map((box, index) => ({
982
+ ...box,
983
+ id: `__endpoint_boundary_${index}`,
984
+ isContainer: true
985
+ }));
986
+ const realLeafObstacles = leafObstacles.filter((o) => !o.id.startsWith("__"));
987
+ const edgeGuardObstacles = leafObstacles.filter(isEdgeGuardObstacle);
988
+ const straight = preferredStraightRoute(a, b);
989
+ if (straight && hasArrowTerminalClearance(straight, op) && (!bounds || routeFitsBounds(straight, bounds)) && !routeIntersectsAnyInterior(straight, realLeafObstacles, OBSTACLE_PAD) && !routeIntersectsAnyInterior(straight, hardLeafObstacles, OBSTACLE_PAD) && (!strictEdgeGuards || !routeSharesAnyEdgeGuardLane(straight, edgeGuardObstacles)) && !routeIntersectsAnyInterior(straight, blockedContainerObstacles, OBSTACLE_PAD) && !routeIntersectsAnyInterior(straight, endpointInteriorBarriers, 0) && !routeOverlapsAnyBorder(straight, [
990
+ ...endpointBoundaries,
991
+ ...passableContainerObstacles,
992
+ ...blockedContainerObstacles
993
+ ], BORDER_CLEARANCE)) {
994
+ return straight;
995
+ }
996
+ const lanes = routeLanes(a, b, [...routedObstacles, ...boundaryObstacles], bounds);
997
+ let best = orthogonalRoute(a, b);
998
+ let bestScore = Number.POSITIVE_INFINITY;
999
+ for (const start of portsOf(a)) {
1000
+ for (const end of portsOf(b)) {
1001
+ const sourcePort = start.point;
1002
+ const targetPort = end.point;
1003
+ const sp = portLeadPoint(start);
1004
+ const ep = portLeadPoint(end);
1005
+ const candidates = [
1006
+ [sourcePort, sp, [ep[0], sp[1]], ep, targetPort],
1007
+ [sourcePort, sp, [sp[0], ep[1]], ep, targetPort]
1008
+ ];
1009
+ if (Math.abs(sp[0] - ep[0]) < EPS || Math.abs(sp[1] - ep[1]) < EPS) {
1010
+ candidates.push([sourcePort, sp, ep, targetPort]);
1011
+ }
1012
+ for (const x of lanes.xs) {
1013
+ candidates.push([sourcePort, sp, [x, sp[1]], [x, ep[1]], ep, targetPort]);
1014
+ }
1015
+ for (const y of lanes.ys) {
1016
+ candidates.push([sourcePort, sp, [sp[0], y], [ep[0], y], ep, targetPort]);
1017
+ }
1018
+ for (const x of lanes.xs) {
1019
+ for (const y of lanes.ys) {
1020
+ candidates.push([sourcePort, sp, [x, sp[1]], [x, y], [ep[0], y], ep, targetPort]);
1021
+ candidates.push([sourcePort, sp, [sp[0], y], [x, y], [x, ep[1]], ep, targetPort]);
1022
+ }
1023
+ }
1024
+ for (const candidate of candidates) {
1025
+ const normalized = normalizeRoute(candidate);
1026
+ if (!isOrthogonalRoute(normalized)) continue;
1027
+ if (!hasArrowTerminalClearance(normalized, op)) continue;
1028
+ if (bounds && !routeFitsBounds(normalized, bounds)) continue;
1029
+ if (routeOverlapsAnyBorder(normalized, [a, b], 0)) continue;
1030
+ if (routeIntersectsAnyInterior(normalized, endpointInteriorBarriers, 0)) continue;
1031
+ if (routeIntersectsAnyInterior(normalized, realLeafObstacles, OBSTACLE_PAD)) continue;
1032
+ if (routeIntersectsAnyInterior(normalized, hardLeafObstacles, OBSTACLE_PAD)) continue;
1033
+ if (strictEdgeGuards && routeSharesAnyEdgeGuardLane(normalized, edgeGuardObstacles)) continue;
1034
+ if (routeIntersectsAnyInterior(normalized, blockedContainerObstacles, OBSTACLE_PAD)) continue;
1035
+ if (routeOverlapsAnyBorder(normalized, [
1036
+ ...endpointBoundaries,
1037
+ ...passableContainerObstacles,
1038
+ ...blockedContainerObstacles
1039
+ ], BORDER_CLEARANCE)) continue;
1040
+ const score = scoreRoute(
1041
+ normalized,
1042
+ a,
1043
+ b,
1044
+ leafObstacles,
1045
+ blockedContainerObstacles,
1046
+ passableContainerObstacles,
1047
+ externalContainerObstacles,
1048
+ endpointBoundaries
1049
+ ) + portPairPenalty(a, b, start.side, end.side, op) + start.offsetPenalty + end.offsetPenalty;
1050
+ if (score < bestScore) {
1051
+ best = normalized;
1052
+ bestScore = score;
1053
+ }
1054
+ }
1055
+ }
1056
+ }
1057
+ if (bestScore === Number.POSITIVE_INFINITY) {
1058
+ if (strictEdgeGuards && edgeGuardObstacles.length > 0) {
1059
+ return avoidObstaclesRoute(
1060
+ a,
1061
+ b,
1062
+ from,
1063
+ to,
1064
+ obstacles,
1065
+ parentMap,
1066
+ bounds,
1067
+ endpointBoundaries,
1068
+ endpointInteriorBarriers,
1069
+ op,
1070
+ false
1071
+ );
1072
+ }
1073
+ return orthogonalRoute(a, b);
1074
+ }
1075
+ return best;
1076
+ }
1077
+ function preferredStraightRoute(a, b) {
1078
+ const acx = a.x + a.w / 2;
1079
+ const acy = a.y + a.h / 2;
1080
+ const bcx = b.x + b.w / 2;
1081
+ const bcy = b.y + b.h / 2;
1082
+ if (Math.abs(acy - bcy) < EPS) {
1083
+ if (a.x + a.w <= b.x) return [[a.x + a.w, acy], [b.x, bcy]];
1084
+ if (b.x + b.w <= a.x) return [[a.x, acy], [b.x + b.w, bcy]];
1085
+ }
1086
+ if (Math.abs(acx - bcx) < EPS) {
1087
+ if (a.y + a.h <= b.y) return [[acx, a.y + a.h], [bcx, b.y]];
1088
+ if (b.y + b.h <= a.y) return [[acx, a.y], [bcx, b.y + b.h]];
1089
+ }
1090
+ return void 0;
1091
+ }
1092
+ function hasArrowTerminalClearance(points, op) {
1093
+ if (points.length < 2) return false;
1094
+ if (op !== "line" && op !== "dashed") {
1095
+ if (terminalSegmentLength(points, false) < arrowTerminalClearance(op)) return false;
1096
+ }
1097
+ if (op === "bidir") {
1098
+ if (terminalSegmentLength(points, true) < arrowTerminalClearance(op)) return false;
1099
+ }
1100
+ return true;
1101
+ }
1102
+ function terminalSegmentLength(points, atStart) {
1103
+ if (points.length < 2) return 0;
1104
+ const a = atStart ? points[0] : points[points.length - 1];
1105
+ const b = atStart ? points[1] : points[points.length - 2];
1106
+ return segmentLength(a, b);
1107
+ }
1108
+ function arrowTerminalClearance(op) {
1109
+ return op === "thick" ? THICK_ARROW_TERMINAL_CLEARANCE : ARROW_TERMINAL_CLEARANCE;
1110
+ }
1111
+ function routeIntersectsAnyInterior(points, boxes, pad) {
1112
+ for (let i = 0; i < points.length - 1; i++) {
1113
+ for (const box of boxes) {
1114
+ if (boxInteriorOverlap(points[i], points[i + 1], box, pad) > EPS) return true;
1115
+ }
1116
+ }
1117
+ return false;
1118
+ }
1119
+ function routeOverlapsAnyBorder(points, boxes, tolerance) {
1120
+ for (let i = 0; i < points.length - 1; i++) {
1121
+ for (const box of boxes) {
1122
+ if (boxBorderProximityOverlap(points[i], points[i + 1], box, tolerance) > EPS) return true;
1123
+ }
1124
+ }
1125
+ return false;
1126
+ }
1127
+ function routeSharesAnyEdgeGuardLane(points, guards) {
1128
+ for (let i = 0; i < points.length - 1; i++) {
1129
+ for (const guard of guards) {
1130
+ if (segmentSharesEdgeGuardLane(points[i], points[i + 1], guard)) return true;
1131
+ }
1132
+ }
1133
+ return false;
1134
+ }
1135
+ function segmentSharesEdgeGuardLane(a, b, guard) {
1136
+ const segmentVertical = Math.abs(a[0] - b[0]) < EPS;
1137
+ const segmentHorizontal = Math.abs(a[1] - b[1]) < EPS;
1138
+ if (segmentVertical && guard.orientation === "vertical") {
1139
+ const guardX = guard.x + guard.w / 2;
1140
+ if (Math.abs(a[0] - guardX) > guard.w / 2 + EPS) return false;
1141
+ return intervalOverlap(a[1], b[1], guard.y, guard.y + guard.h) > EPS;
1142
+ }
1143
+ if (segmentHorizontal && guard.orientation === "horizontal") {
1144
+ const guardY = guard.y + guard.h / 2;
1145
+ if (Math.abs(a[1] - guardY) > guard.h / 2 + EPS) return false;
1146
+ return intervalOverlap(a[0], b[0], guard.x, guard.x + guard.w) > EPS;
1147
+ }
1148
+ return false;
1149
+ }
1150
+ function allowedContainerIds(from, to, parentMap) {
1151
+ return /* @__PURE__ */ new Set([from, to, ...ancestorsOf(from, parentMap), ...ancestorsOf(to, parentMap)]);
1152
+ }
1153
+ function portsOf(box) {
1154
+ const cx = box.x + box.w / 2;
1155
+ const cy = box.y + box.h / 2;
1156
+ const xOffset = Math.min(box.w * 0.25, Math.max(3, box.w / 2 - 4));
1157
+ const yOffset = Math.min(box.h * 0.25, Math.max(2, box.h / 2 - 3));
1158
+ const sidePenalty = 9;
1159
+ return [
1160
+ { point: [box.x + box.w, cy], side: "right", offsetPenalty: 0 },
1161
+ { point: [box.x, cy], side: "left", offsetPenalty: 0 },
1162
+ { point: [cx, box.y + box.h], side: "bottom", offsetPenalty: 0 },
1163
+ { point: [cx, box.y], side: "top", offsetPenalty: 0 },
1164
+ { point: [box.x + box.w, cy - yOffset], side: "right", offsetPenalty: sidePenalty },
1165
+ { point: [box.x + box.w, cy + yOffset], side: "right", offsetPenalty: sidePenalty },
1166
+ { point: [box.x, cy - yOffset], side: "left", offsetPenalty: sidePenalty },
1167
+ { point: [box.x, cy + yOffset], side: "left", offsetPenalty: sidePenalty },
1168
+ { point: [cx - xOffset, box.y + box.h], side: "bottom", offsetPenalty: sidePenalty },
1169
+ { point: [cx + xOffset, box.y + box.h], side: "bottom", offsetPenalty: sidePenalty },
1170
+ { point: [cx - xOffset, box.y], side: "top", offsetPenalty: sidePenalty },
1171
+ { point: [cx + xOffset, box.y], side: "top", offsetPenalty: sidePenalty }
1172
+ ];
1173
+ }
1174
+ function portLeadPoint(port) {
1175
+ const [x, y] = port.point;
1176
+ switch (port.side) {
1177
+ case "right":
1178
+ return [x + PORT_STUB, y];
1179
+ case "left":
1180
+ return [x - PORT_STUB, y];
1181
+ case "bottom":
1182
+ return [x, y + PORT_STUB];
1183
+ case "top":
1184
+ return [x, y - PORT_STUB];
1185
+ }
1186
+ }
1187
+ function portPairPenalty(source, target, start, end, op) {
1188
+ const sx = source.x + source.w / 2;
1189
+ const sy = source.y + source.h / 2;
1190
+ const tx = target.x + target.w / 2;
1191
+ const ty = target.y + target.h / 2;
1192
+ const dx = tx - sx;
1193
+ const dy = ty - sy;
1194
+ if (op === "dashed") return dashedPortPairPenalty(start, end, dx, dy, source, target);
1195
+ return sourcePortPenalty(start, dx, dy, source) + targetPortPenalty(end, dx, dy, target);
1196
+ }
1197
+ function dashedPortPairPenalty(start, end, dx, dy, source, target) {
1198
+ if (Math.abs(dy) > Math.min(source.h, target.h) * 0.8) {
1199
+ const startDesired = dy > 0 ? "bottom" : "top";
1200
+ const endDesired = dy > 0 ? "top" : "bottom";
1201
+ return dashedSidePenalty(start, startDesired) + dashedSidePenalty(end, endDesired);
1202
+ }
1203
+ if (Math.abs(dx) > Math.min(source.w, target.w) * 0.8) {
1204
+ const startDesired = dx > 0 ? "right" : "left";
1205
+ const endDesired = dx > 0 ? "left" : "right";
1206
+ return dashedSidePenalty(start, startDesired) + dashedSidePenalty(end, endDesired);
1207
+ }
1208
+ return 0;
1209
+ }
1210
+ function dashedSidePenalty(side, desired) {
1211
+ if (side === desired) return 0;
1212
+ if (side === oppositeSide(desired)) return 1400;
1213
+ return 450;
1214
+ }
1215
+ function sourcePortPenalty(side, dx, dy, box) {
1216
+ if (Math.abs(dy) > box.h * 0.8 && Math.abs(dy) >= Math.abs(dx) * VERTICAL_PORT_RATIO) {
1217
+ const desired = dy > 0 ? "bottom" : "top";
1218
+ if (side === desired) return 0;
1219
+ return side === oppositeSide(desired) ? 800 : 180;
1220
+ }
1221
+ if (Math.abs(dx) > box.w * 0.5) {
1222
+ const desired = dx > 0 ? "right" : "left";
1223
+ if (side === desired) return 0;
1224
+ return side === oppositeSide(desired) ? 900 : 90;
1225
+ }
1226
+ if (Math.abs(dy) > box.h * 0.8) {
1227
+ const desired = dy > 0 ? "bottom" : "top";
1228
+ if (side === desired) return 0;
1229
+ return side === oppositeSide(desired) ? 600 : 120;
1230
+ }
1231
+ return 0;
1232
+ }
1233
+ function targetPortPenalty(side, dx, dy, box) {
1234
+ if (Math.abs(dy) > box.h * 0.8 && Math.abs(dy) >= Math.abs(dx) * VERTICAL_PORT_RATIO) {
1235
+ const desired = dy > 0 ? "top" : "bottom";
1236
+ if (side === desired) return 0;
1237
+ return side === oppositeSide(desired) ? 1400 : 360;
1238
+ }
1239
+ if (Math.abs(dx) > box.w * 0.5 && Math.abs(dx) >= Math.abs(dy) * 1.2) {
1240
+ const desired = dx > 0 ? "left" : "right";
1241
+ if (side === desired) return 0;
1242
+ return side === oppositeSide(desired) ? 1200 : 220;
1243
+ }
1244
+ if (Math.abs(dy) > box.h * 0.35) {
1245
+ const desired = dy > 0 ? "top" : "bottom";
1246
+ if (side === desired) return 0;
1247
+ return side === "left" || side === "right" ? 9e3 : 1200;
1248
+ }
1249
+ if (Math.abs(dx) > box.w * 0.5) {
1250
+ const desired = dx > 0 ? "left" : "right";
1251
+ if (side === desired) return 0;
1252
+ return side === oppositeSide(desired) ? 1e3 : 160;
1253
+ }
1254
+ return 0;
1255
+ }
1256
+ function oppositeSide(side) {
1257
+ switch (side) {
1258
+ case "right":
1259
+ return "left";
1260
+ case "left":
1261
+ return "right";
1262
+ case "bottom":
1263
+ return "top";
1264
+ case "top":
1265
+ return "bottom";
1266
+ }
1267
+ }
1268
+ function routeLanes(a, b, obstacles, bounds) {
1269
+ const boxes = [a, b, ...obstacles];
1270
+ const laneMargin = ROUTE_GAP * 2;
1271
+ const minX = bounds ? bounds.x : Math.max(MARGIN, Math.min(...boxes.map((o) => o.x)) - laneMargin);
1272
+ const maxX = bounds ? bounds.x + bounds.w : Math.max(...boxes.map((o) => o.x + o.w)) + laneMargin;
1273
+ const minY = bounds ? bounds.y : Math.max(MARGIN, Math.min(...boxes.map((o) => o.y)) - laneMargin);
1274
+ const maxY = bounds ? bounds.y + bounds.h : Math.max(...boxes.map((o) => o.y + o.h)) + laneMargin;
1275
+ const xs = [(a.x + a.w / 2 + b.x + b.w / 2) / 2];
1276
+ const ys = [(a.y + a.h / 2 + b.y + b.h / 2) / 2];
1277
+ for (const box of boxes) {
1278
+ for (const gap of [ROUTE_GAP, ROUTE_GAP * 1.5, ROUTE_GAP * 2]) {
1279
+ xs.push(box.x - gap, box.x + box.w + gap);
1280
+ ys.push(box.y - gap, box.y + box.h + gap);
1281
+ }
1282
+ }
1283
+ if (bounds) {
1284
+ xs.push(bounds.x, bounds.x + bounds.w);
1285
+ ys.push(bounds.y, bounds.y + bounds.h);
1286
+ }
1287
+ return {
1288
+ xs: limitRouteLanes(
1289
+ uniqueSorted(xs.filter((x) => x >= minX && x <= maxX)),
1290
+ (a.x + a.w / 2 + b.x + b.w / 2) / 2
1291
+ ),
1292
+ ys: limitRouteLanes(
1293
+ uniqueSorted(ys.filter((y) => y >= minY && y <= maxY)),
1294
+ (a.y + a.h / 2 + b.y + b.h / 2) / 2
1295
+ )
1296
+ };
1297
+ }
1298
+ function limitRouteLanes(values, anchor) {
1299
+ if (values.length <= MAX_ROUTE_LANES) return values;
1300
+ const keep = /* @__PURE__ */ new Set([values[0], values[values.length - 1]]);
1301
+ for (const value of [...values].sort((a, b) => Math.abs(a - anchor) - Math.abs(b - anchor))) {
1302
+ keep.add(value);
1303
+ if (keep.size >= MAX_ROUTE_LANES) break;
1304
+ }
1305
+ return [...keep].sort((a, b) => a - b);
1306
+ }
1307
+ function commonRoutingBounds(from, to, parentMap, positions) {
1308
+ const common = nearestCommonAncestor(from, to, parentMap);
1309
+ if (!common) return void 0;
1310
+ const box = positions.get(common);
1311
+ if (!box) return void 0;
1312
+ const inset = ROUTE_GAP / 2;
1313
+ const top = box.y + TITLE_H + PAD / 2;
1314
+ const bottom = box.y + box.h - inset;
1315
+ return {
1316
+ x: box.x + inset,
1317
+ y: top,
1318
+ w: Math.max(0, box.w - inset * 2),
1319
+ h: Math.max(0, bottom - top)
1320
+ };
1321
+ }
1322
+ function nearestCommonAncestor(from, to, parentMap) {
1323
+ const toAncestors = new Set(ancestorsOf(to, parentMap));
1324
+ return ancestorsOf(from, parentMap).find((id) => toAncestors.has(id));
1325
+ }
1326
+ function ancestorsOf(id, parentMap) {
1327
+ const out = [];
1328
+ let cur = id;
1329
+ const seen = /* @__PURE__ */ new Set();
1330
+ while (parentMap.has(cur) && !seen.has(cur)) {
1331
+ seen.add(cur);
1332
+ cur = parentMap.get(cur);
1333
+ out.push(cur);
1334
+ }
1335
+ return out;
1336
+ }
1337
+ function routeFitsBounds(points, bounds) {
1338
+ const right = bounds.x + bounds.w;
1339
+ const bottom = bounds.y + bounds.h;
1340
+ return points.every(([x, y]) => x >= bounds.x - EPS && x <= right + EPS && y >= bounds.y - EPS && y <= bottom + EPS);
1341
+ }
1342
+ function uniqueSorted(values) {
1343
+ const seen = /* @__PURE__ */ new Set();
1344
+ const out = [];
1345
+ for (const value of values) {
1346
+ const rounded = Math.round(value * 1e3) / 1e3;
1347
+ const key = rounded.toFixed(3);
1348
+ if (seen.has(key)) continue;
1349
+ seen.add(key);
1350
+ out.push(rounded);
1351
+ }
1352
+ return out.sort((a, b) => a - b);
1353
+ }
1354
+ function normalizeRoute(points) {
1355
+ const deduped = [];
1356
+ for (const point of points) {
1357
+ const prev = deduped[deduped.length - 1];
1358
+ if (!prev || Math.abs(prev[0] - point[0]) >= EPS || Math.abs(prev[1] - point[1]) >= EPS) {
1359
+ deduped.push(point);
1360
+ }
1361
+ }
1362
+ const out = [];
1363
+ for (const point of deduped) {
1364
+ out.push(point);
1365
+ while (out.length >= 3) {
1366
+ const a = out[out.length - 3];
1367
+ const b = out[out.length - 2];
1368
+ const c = out[out.length - 1];
1369
+ const sameX = Math.abs(a[0] - b[0]) < EPS && Math.abs(b[0] - c[0]) < EPS;
1370
+ const sameY = Math.abs(a[1] - b[1]) < EPS && Math.abs(b[1] - c[1]) < EPS;
1371
+ if (!sameX && !sameY) break;
1372
+ out.splice(out.length - 2, 1);
1373
+ }
1374
+ }
1375
+ return out;
1376
+ }
1377
+ function isOrthogonalRoute(points) {
1378
+ if (points.length < 2) return false;
1379
+ for (let i = 0; i < points.length - 1; i++) {
1380
+ const a = points[i];
1381
+ const b = points[i + 1];
1382
+ if (Math.abs(a[0] - b[0]) >= EPS && Math.abs(a[1] - b[1]) >= EPS) return false;
1383
+ }
1384
+ return true;
1385
+ }
1386
+ function scoreRoute(points, source, target, leafObstacles, blockedContainerObstacles, passableContainerObstacles, externalContainerObstacles, endpointBoundaries) {
1387
+ let score = 0;
1388
+ for (let i = 0; i < points.length - 1; i++) {
1389
+ const a = points[i];
1390
+ const b = points[i + 1];
1391
+ score += segmentLength(a, b);
1392
+ const sourceInterior = boxInteriorOverlap(a, b, source, 0);
1393
+ if (sourceInterior > 0) score += 22e4 + sourceInterior * 1500;
1394
+ const targetInterior = boxInteriorOverlap(a, b, target, 0);
1395
+ if (targetInterior > 0) score += 22e4 + targetInterior * 1500;
1396
+ const sourceBorder = boxBorderOverlap(a, b, source);
1397
+ if (sourceBorder > 0) score += 8e3 + sourceBorder * 120;
1398
+ const targetBorder = boxBorderOverlap(a, b, target);
1399
+ if (targetBorder > 0) score += 8e3 + targetBorder * 120;
1400
+ for (const boundary of endpointBoundaries) {
1401
+ const overlap = boxBorderOverlap(a, b, boundary);
1402
+ if (overlap > 0) score += 12e3 + overlap * 160;
1403
+ }
1404
+ for (const obstacle of leafObstacles) {
1405
+ const overlap = boxInteriorOverlap(a, b, obstacle, OBSTACLE_PAD);
1406
+ if (overlap > 0) score += leafObstaclePenalty(obstacle, a, b, overlap);
1407
+ }
1408
+ for (const obstacle of blockedContainerObstacles) {
1409
+ const overlap = boxInteriorOverlap(a, b, obstacle, OBSTACLE_PAD);
1410
+ if (overlap > 0) score += 18e4 + overlap * 1200;
1411
+ const border = boxBorderOverlap(a, b, obstacle);
1412
+ if (border > 0) score += 18e3 + border * 300;
1413
+ }
1414
+ for (const obstacle of passableContainerObstacles) {
1415
+ const titleOverlap = boxInteriorOverlap(a, b, containerTitleRoutingBand(obstacle), OBSTACLE_PAD);
1416
+ if (titleOverlap > 0) score += 14e3 + titleOverlap * 120;
1417
+ const overlap = boxBorderOverlap(a, b, obstacle);
1418
+ if (overlap > 0) score += 6e3 + overlap * 120;
1419
+ }
1420
+ for (const obstacle of externalContainerObstacles) {
1421
+ const overlap = boxInteriorOverlap(a, b, obstacle, 0);
1422
+ if (overlap > 0) score += overlap * 40;
1423
+ }
1424
+ }
1425
+ score += Math.max(0, points.length - 2) * 3;
1426
+ score += routeExcursionPenalty(points, source, target);
1427
+ return score;
1428
+ }
1429
+ function routeExcursionPenalty(points, source, target) {
1430
+ const pad = ROUTE_GAP * 5;
1431
+ const left = Math.min(source.x, target.x) - pad;
1432
+ const top = Math.min(source.y, target.y) - pad;
1433
+ const right = Math.max(source.x + source.w, target.x + target.w) + pad;
1434
+ const bottom = Math.max(source.y + source.h, target.y + target.h) + pad;
1435
+ let penalty = 0;
1436
+ for (const [x, y] of points) {
1437
+ if (x < left) penalty += (left - x) * 90;
1438
+ if (x > right) penalty += (x - right) * 90;
1439
+ if (y < top) penalty += (top - y) * 90;
1440
+ if (y > bottom) penalty += (y - bottom) * 90;
1441
+ }
1442
+ return penalty;
1443
+ }
1444
+ function leafObstaclePenalty(obstacle, a, b, overlap) {
1445
+ if (obstacle.id.startsWith("__arrow_guard_")) return 7e4 + overlap * 700;
1446
+ if (isEdgeGuardObstacle(obstacle)) return edgeGuardPenalty(obstacle, a, b, overlap);
1447
+ return 16e4 + overlap * 1200;
1448
+ }
1449
+ function edgeGuardPenalty(obstacle, a, b, overlap) {
1450
+ const segmentVertical = Math.abs(a[0] - b[0]) < EPS;
1451
+ const guardVertical = obstacle.orientation === "vertical";
1452
+ if (segmentVertical === guardVertical) return 85e3 + overlap * 1200;
1453
+ return 500 + overlap * 160;
1454
+ }
1455
+ function isEdgeGuardObstacle(obstacle) {
1456
+ return obstacle.id.startsWith("__edge_guard_") && "orientation" in obstacle && (obstacle.orientation === "vertical" || obstacle.orientation === "horizontal");
1457
+ }
1458
+ function containerTitleRoutingBand(container) {
1459
+ return {
1460
+ x: container.x,
1461
+ y: container.y,
1462
+ w: container.w,
1463
+ h: TITLE_H + PAD
1464
+ };
1465
+ }
1466
+ function segmentLength(a, b) {
1467
+ return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);
1468
+ }
1469
+ function boxInteriorOverlap(a, b, box, pad) {
1470
+ const left = box.x - pad;
1471
+ const right = box.x + box.w + pad;
1472
+ const top = box.y - pad;
1473
+ const bottom = box.y + box.h + pad;
1474
+ if (Math.abs(a[0] - b[0]) < EPS) {
1475
+ const x = a[0];
1476
+ if (x <= left || x >= right) return 0;
1477
+ return intervalOverlap(a[1], b[1], top, bottom);
1478
+ }
1479
+ if (Math.abs(a[1] - b[1]) < EPS) {
1480
+ const y = a[1];
1481
+ if (y <= top || y >= bottom) return 0;
1482
+ return intervalOverlap(a[0], b[0], left, right);
1483
+ }
1484
+ return 0;
1485
+ }
1486
+ function boxBorderOverlap(a, b, box) {
1487
+ return boxBorderProximityOverlap(a, b, box, 0);
1488
+ }
1489
+ function boxBorderProximityOverlap(a, b, box, tolerance) {
1490
+ if (Math.abs(a[0] - b[0]) < EPS) {
1491
+ const x = a[0];
1492
+ if (Math.abs(x - box.x) > tolerance + EPS && Math.abs(x - (box.x + box.w)) > tolerance + EPS) return 0;
1493
+ return intervalOverlap(a[1], b[1], box.y, box.y + box.h);
1494
+ }
1495
+ if (Math.abs(a[1] - b[1]) < EPS) {
1496
+ const y = a[1];
1497
+ if (Math.abs(y - box.y) > tolerance + EPS && Math.abs(y - (box.y + box.h)) > tolerance + EPS) return 0;
1498
+ return intervalOverlap(a[0], b[0], box.x, box.x + box.w);
1499
+ }
1500
+ return 0;
1501
+ }
1502
+ function intervalOverlap(a1, a2, b1, b2) {
1503
+ const minA = Math.min(a1, a2);
1504
+ const maxA = Math.max(a1, a2);
1505
+ const minB = Math.min(b1, b2);
1506
+ const maxB = Math.max(b1, b2);
1507
+ return Math.max(0, Math.min(maxA, maxB) - Math.max(minA, minB));
1508
+ }
1509
+ function orthogonalRoute(a, b) {
1510
+ const ca = { x: a.x + a.w / 2, y: a.y + a.h / 2 };
1511
+ const cb = { x: b.x + b.w / 2, y: b.y + b.h / 2 };
1512
+ const dx = cb.x - ca.x;
1513
+ const dy = cb.y - ca.y;
1514
+ let pa, pb;
1515
+ const separatedHorizontally = a.x + a.w <= b.x || b.x + b.w <= a.x;
1516
+ const separatedVertically = a.y + a.h <= b.y || b.y + b.h <= a.y;
1517
+ const horizontal = separatedHorizontally ? true : separatedVertically ? false : Math.abs(dx) > Math.abs(dy);
1518
+ if (horizontal) {
1519
+ pa = [dx > 0 ? a.x + a.w : a.x, ca.y];
1520
+ pb = [dx > 0 ? b.x : b.x + b.w, cb.y];
1521
+ if (Math.abs(pa[1] - pb[1]) < 0.5) return [pa, pb];
1522
+ const midX = (pa[0] + pb[0]) / 2;
1523
+ return [pa, [midX, pa[1]], [midX, pb[1]], pb];
1524
+ } else {
1525
+ pa = [ca.x, dy > 0 ? a.y + a.h : a.y];
1526
+ pb = [cb.x, dy > 0 ? b.y : b.y + b.h];
1527
+ if (Math.abs(pa[0] - pb[0]) < 0.5) return [pa, pb];
1528
+ const midY = (pa[1] + pb[1]) / 2;
1529
+ return [pa, [pa[0], midY], [pb[0], midY], pb];
1530
+ }
1531
+ }
1532
+
1533
+ // src/core/render.ts
1534
+ var NS = "http://www.w3.org/2000/svg";
1535
+ var FONT_FAMILY = '"Hiragino Sans", "Yu Gothic", "Noto Sans CJK JP", sans-serif';
1536
+ var LABEL_OFFSET = 4;
1537
+ var LABEL_FONT_JA = 2.4;
1538
+ var LABEL_FONT_EN = 2.1;
1539
+ var LABEL_GAP = 0.6;
1540
+ var LABEL_OFFSET_STEPS = [0, 3.2, 6.4];
1541
+ var EPS2 = 1e-3;
1542
+ function render(laid, opts = {}) {
1543
+ const lang = opts.lang ?? "ja";
1544
+ const svg = el("svg", {
1545
+ xmlns: NS,
1546
+ viewBox: `0 0 ${laid.width} ${laid.height}`,
1547
+ "font-family": FONT_FAMILY,
1548
+ "shape-rendering": "geometricPrecision"
1549
+ });
1550
+ const containers = laid.nodes.filter((n) => n.isContainer);
1551
+ const leaves = laid.nodes.filter((n) => !n.isContainer);
1552
+ const labelBoxes = [];
1553
+ for (const c of containers) svg.appendChild(renderContainer(c, lang));
1554
+ for (const e of laid.edges) svg.appendChild(renderEdge(e, lang, laid, labelBoxes));
1555
+ for (const e of laid.edges) svg.appendChild(renderEdgeHeads(e));
1556
+ for (const n of leaves) svg.appendChild(renderNode(n, lang));
1557
+ return svg;
1558
+ }
1559
+ function renderContainer(n, lang) {
1560
+ const g = el("g");
1561
+ g.appendChild(el("rect", {
1562
+ x: n.x,
1563
+ y: n.y,
1564
+ width: n.w,
1565
+ height: n.h,
1566
+ fill: "white",
1567
+ stroke: "#000",
1568
+ "stroke-width": 0.3
1569
+ }));
1570
+ const labels = pickLabel(n.label, lang);
1571
+ if (labels.length || n.id) {
1572
+ const t = el("text", {
1573
+ x: n.x + 2,
1574
+ y: n.y + 3.5,
1575
+ "font-size": 2.6,
1576
+ fill: "#000"
1577
+ });
1578
+ t.textContent = (n.id ? n.id + " " : "") + (labels[0] ?? "");
1579
+ g.appendChild(t);
1580
+ }
1581
+ return g;
1582
+ }
1583
+ function renderNode(n, lang) {
1584
+ const g = el("g");
1585
+ g.appendChild(renderShape(n));
1586
+ const lines = [];
1587
+ if (n.id && n.id !== "*") lines.push(n.id);
1588
+ const labels = pickLabel(n.label, lang);
1589
+ for (const l of labels) lines.push(l);
1590
+ if (lines.length === 0) return g;
1591
+ const fontSize = 2.8;
1592
+ const lineH = fontSize * 1.2;
1593
+ const totalH = lines.length * lineH;
1594
+ const startY = n.y + n.h / 2 - totalH / 2 + lineH * 0.8;
1595
+ for (let i = 0; i < lines.length; i++) {
1596
+ const t = el("text", {
1597
+ x: n.x + n.w / 2,
1598
+ y: startY + i * lineH,
1599
+ "font-size": fontSize,
1600
+ fill: "#000",
1601
+ "text-anchor": "middle"
1602
+ });
1603
+ t.textContent = lines[i];
1604
+ g.appendChild(t);
1605
+ }
1606
+ return g;
1607
+ }
1608
+ function renderShape(n) {
1609
+ const stroke = "#000";
1610
+ const fill = "white";
1611
+ const sw = 0.4;
1612
+ switch (n.shape) {
1613
+ case "round":
1614
+ return el("rect", {
1615
+ x: n.x,
1616
+ y: n.y,
1617
+ width: n.w,
1618
+ height: n.h,
1619
+ rx: Math.min(n.w, n.h) / 2,
1620
+ ry: Math.min(n.w, n.h) / 2,
1621
+ fill,
1622
+ stroke,
1623
+ "stroke-width": sw
1624
+ });
1625
+ case "circle": {
1626
+ const r = Math.min(n.w, n.h) / 2;
1627
+ return el("circle", {
1628
+ cx: n.x + n.w / 2,
1629
+ cy: n.y + n.h / 2,
1630
+ r,
1631
+ fill: "#000",
1632
+ stroke
1633
+ });
1634
+ }
1635
+ case "diamond": {
1636
+ const cx = n.x + n.w / 2;
1637
+ const cy = n.y + n.h / 2;
1638
+ const pts = [[cx, n.y], [n.x + n.w, cy], [cx, n.y + n.h], [n.x, cy]].map((p) => p.join(",")).join(" ");
1639
+ return el("polygon", { points: pts, fill, stroke, "stroke-width": sw });
1640
+ }
1641
+ case "actor":
1642
+ case "box":
1643
+ default:
1644
+ return el("rect", {
1645
+ x: n.x,
1646
+ y: n.y,
1647
+ width: n.w,
1648
+ height: n.h,
1649
+ fill,
1650
+ stroke,
1651
+ "stroke-width": sw
1652
+ });
1653
+ }
1654
+ }
1655
+ function renderEdge(e, lang, laid, labelBoxes) {
1656
+ const g = el("g");
1657
+ if (e.points.length < 2) return g;
1658
+ const bodyPoints = edgeBodyPoints(e);
1659
+ if (bodyPoints.length < 2) return g;
1660
+ const d = bodyPoints.map((p, i) => i === 0 ? `M ${p[0]} ${p[1]}` : `L ${p[0]} ${p[1]}`).join(" ");
1661
+ const path = el("path", {
1662
+ d,
1663
+ fill: "none",
1664
+ stroke: "#000",
1665
+ "stroke-width": strokeWidth(e.op),
1666
+ "stroke-linecap": "butt",
1667
+ "stroke-linejoin": "miter"
1668
+ });
1669
+ const dash = strokeDash(e.op);
1670
+ if (dash) path.setAttribute("stroke-dasharray", dash);
1671
+ g.appendChild(path);
1672
+ const labels = pickLabel(e.label, lang);
1673
+ if (labels.length) {
1674
+ const ja = labels[0];
1675
+ const en = labels[1];
1676
+ const placement = chooseLabelPlacement(e, labels, laid, labelBoxes);
1677
+ labelBoxes.push(expandBox(placement.box, 0.8));
1678
+ const drawText = (text, x, y, fontSize, anchor, baseline) => {
1679
+ const t = el("text", {
1680
+ x,
1681
+ y,
1682
+ "font-size": fontSize,
1683
+ fill: "#000",
1684
+ "text-anchor": anchor,
1685
+ "dominant-baseline": baseline
1686
+ });
1687
+ t.textContent = text;
1688
+ g.appendChild(t);
1689
+ };
1690
+ drawText(ja, placement.ja.x, placement.ja.y, LABEL_FONT_JA, placement.anchor, placement.ja.baseline);
1691
+ if (en && placement.en) {
1692
+ drawText(en, placement.en.x, placement.en.y, LABEL_FONT_EN, placement.anchor, placement.en.baseline);
1693
+ }
1694
+ }
1695
+ return g;
1696
+ }
1697
+ function renderEdgeHeads(e) {
1698
+ const g = el("g");
1699
+ if (e.isLifeline) return g;
1700
+ const endHead = arrowHeadFor(e, false);
1701
+ const startHead = arrowHeadFor(e, true);
1702
+ if (endHead) g.appendChild(endHead);
1703
+ if (startHead) g.appendChild(startHead);
1704
+ return g;
1705
+ }
1706
+ function strokeWidth(op) {
1707
+ return op === "thick" ? 0.7 : 0.4;
1708
+ }
1709
+ function strokeDash(op) {
1710
+ return op === "dashed" || op === "dashed-arrow" ? "1.4 1.2" : null;
1711
+ }
1712
+ function edgeBodyPoints(e) {
1713
+ let points = e.points.map((p) => [p[0], p[1]]);
1714
+ if (e.op !== "line" && e.op !== "dashed") {
1715
+ points = trimPolyline(points, false, arrowLength(e.op));
1716
+ }
1717
+ if (e.op === "bidir") {
1718
+ points = trimPolyline(points, true, arrowLength(e.op));
1719
+ }
1720
+ return points;
1721
+ }
1722
+ function trimPolyline(points, atStart, amount) {
1723
+ if (points.length < 2 || amount <= 0) return points;
1724
+ const out = atStart ? [...points].reverse() : [...points];
1725
+ let remaining = amount;
1726
+ while (out.length >= 2 && remaining > 0) {
1727
+ const tip = out[out.length - 1];
1728
+ const prev = out[out.length - 2];
1729
+ const dx = prev[0] - tip[0];
1730
+ const dy = prev[1] - tip[1];
1731
+ const len = Math.hypot(dx, dy);
1732
+ if (len < EPS2) {
1733
+ out.pop();
1734
+ continue;
1735
+ }
1736
+ if (len > remaining) {
1737
+ out[out.length - 1] = [
1738
+ tip[0] + dx / len * remaining,
1739
+ tip[1] + dy / len * remaining
1740
+ ];
1741
+ remaining = 0;
1742
+ } else {
1743
+ out.pop();
1744
+ remaining -= len;
1745
+ }
1746
+ }
1747
+ return atStart ? out.reverse() : out;
1748
+ }
1749
+ function arrowLength(op) {
1750
+ return op === "thick" ? 3.4 : 2.8;
1751
+ }
1752
+ function arrowHeadFor(e, atStart) {
1753
+ if (e.points.length < 2) return null;
1754
+ if (atStart && e.op !== "bidir") return null;
1755
+ if (!atStart && (e.op === "line" || e.op === "dashed")) return null;
1756
+ const points = atStart ? e.points : [...e.points].reverse();
1757
+ return renderArrowHead(points[0], points[1], e.op === "thick");
1758
+ }
1759
+ function renderArrowHead(tip, next, bold) {
1760
+ const dx = next[0] - tip[0];
1761
+ const dy = next[1] - tip[1];
1762
+ const len = Math.hypot(dx, dy);
1763
+ if (len < 1e-3) return null;
1764
+ const ux = dx / len;
1765
+ const uy = dy / len;
1766
+ const arrowLen = bold ? 3.4 : 2.8;
1767
+ const halfW = bold ? 1.6 : 1.25;
1768
+ const bx = tip[0] + ux * arrowLen;
1769
+ const by = tip[1] + uy * arrowLen;
1770
+ const px = -uy;
1771
+ const py = ux;
1772
+ const pts = [
1773
+ tip,
1774
+ [bx + px * halfW, by + py * halfW],
1775
+ [bx - px * halfW, by - py * halfW]
1776
+ ].map((p) => p.join(",")).join(" ");
1777
+ return el("polygon", {
1778
+ points: pts,
1779
+ fill: "#000",
1780
+ stroke: "#000",
1781
+ "stroke-width": 0
1782
+ });
1783
+ }
1784
+ function el(tag, attrs = {}) {
1785
+ const node = document.createElementNS(NS, tag);
1786
+ for (const [k, v] of Object.entries(attrs)) node.setAttribute(k, String(v));
1787
+ return node;
1788
+ }
1789
+ function pickLabel(b, lang) {
1790
+ if (!b) return [];
1791
+ if (lang === "ja") return b.ja ? [b.ja] : b.en ? [b.en] : [];
1792
+ if (lang === "en") return b.en ? [b.en] : b.ja ? [b.ja] : [];
1793
+ const out = [];
1794
+ if (b.ja) out.push(b.ja);
1795
+ if (b.en) out.push(b.en);
1796
+ return out;
1797
+ }
1798
+ function chooseLabelPlacement(edge, labels, laid, occupiedLabels = []) {
1799
+ const candidates = [];
1800
+ const textW = Math.max(...labels.map((label, index) => estimateTextWidth(
1801
+ label,
1802
+ index === 0 ? LABEL_FONT_JA : LABEL_FONT_EN
1803
+ )));
1804
+ const textH = labels.length > 1 ? LABEL_FONT_JA + LABEL_FONT_EN + LABEL_GAP : LABEL_FONT_JA;
1805
+ for (let i = 0; i < edge.points.length - 1; i++) {
1806
+ const a = edge.points[i];
1807
+ const b = edge.points[i + 1];
1808
+ const dx = b[0] - a[0];
1809
+ const dy = b[1] - a[1];
1810
+ const len = Math.abs(dx) + Math.abs(dy);
1811
+ if (len < 8) continue;
1812
+ for (const t of [0.5, 0.35, 0.65, 0.22, 0.78]) {
1813
+ const x = a[0] + dx * t;
1814
+ const y = a[1] + dy * t;
1815
+ for (const offsetStep of LABEL_OFFSET_STEPS) {
1816
+ const offset = LABEL_OFFSET + offsetStep;
1817
+ const offsetPenalty = offsetStep * 9;
1818
+ if (Math.abs(dy) < EPS2) {
1819
+ candidates.push(makeHorizontalLabel(edge, laid, occupiedLabels, labels, textW, textH, a, b, x, y, "above", offset, offsetPenalty));
1820
+ candidates.push(makeHorizontalLabel(edge, laid, occupiedLabels, labels, textW, textH, a, b, x, y, "below", offset, offsetPenalty));
1821
+ } else if (Math.abs(dx) < EPS2) {
1822
+ candidates.push(makeVerticalLabel(edge, laid, occupiedLabels, labels, textW, textH, a, b, x, y, "right", offset, offsetPenalty));
1823
+ candidates.push(makeVerticalLabel(edge, laid, occupiedLabels, labels, textW, textH, a, b, x, y, "left", offset, offsetPenalty));
1824
+ }
1825
+ }
1826
+ }
1827
+ }
1828
+ if (candidates.length === 0) {
1829
+ const [a, b] = longestSegment(edge.points);
1830
+ const x = (a[0] + b[0]) / 2;
1831
+ const y = (a[1] + b[1]) / 2;
1832
+ return Math.abs(b[1] - a[1]) >= Math.abs(b[0] - a[0]) ? makeVerticalLabel(edge, laid, occupiedLabels, labels, textW, textH, a, b, x, y, "right", LABEL_OFFSET, 0) : makeHorizontalLabel(edge, laid, occupiedLabels, labels, textW, textH, a, b, x, y, "above", LABEL_OFFSET, 0);
1833
+ }
1834
+ candidates.sort((a, b) => a.score - b.score);
1835
+ return candidates[0];
1836
+ }
1837
+ function makeHorizontalLabel(edge, laid, occupiedLabels, labels, textW, textH, lineA, lineB, x, lineY, side, offset, offsetPenalty) {
1838
+ const top = side === "above" ? lineY - offset - textH : lineY + offset;
1839
+ const box = { x: x - textW / 2, y: top, w: textW, h: textH };
1840
+ let jaY;
1841
+ let enY;
1842
+ if (labels.length > 1) {
1843
+ jaY = top + LABEL_FONT_JA;
1844
+ enY = jaY + LABEL_GAP + LABEL_FONT_EN;
1845
+ } else {
1846
+ jaY = top + LABEL_FONT_JA;
1847
+ }
1848
+ const placement = {
1849
+ box,
1850
+ lineA,
1851
+ lineB,
1852
+ score: 0,
1853
+ vertical: false,
1854
+ anchor: "middle",
1855
+ ja: { x, y: jaY, baseline: "alphabetic" },
1856
+ en: enY === void 0 ? void 0 : { x, y: enY, baseline: "alphabetic" }
1857
+ };
1858
+ placement.score = labelPlacementScore(placement, edge, laid, occupiedLabels) + (side === "above" ? 0 : 8) + offsetPenalty;
1859
+ return placement;
1860
+ }
1861
+ function makeVerticalLabel(edge, laid, occupiedLabels, labels, textW, textH, lineA, lineB, lineX, y, side, offset, offsetPenalty) {
1862
+ const x = side === "right" ? lineX + offset : lineX - offset;
1863
+ const box = {
1864
+ x: side === "right" ? x : x - textW,
1865
+ y: y - textH / 2,
1866
+ w: textW,
1867
+ h: textH
1868
+ };
1869
+ const anchor = side === "right" ? "start" : "end";
1870
+ const jaY = labels.length > 1 ? y - LABEL_FONT_JA / 2 - LABEL_GAP / 2 : y;
1871
+ const enY = labels.length > 1 ? y + LABEL_FONT_EN / 2 + LABEL_GAP / 2 : void 0;
1872
+ const placement = {
1873
+ box,
1874
+ lineA,
1875
+ lineB,
1876
+ score: 0,
1877
+ vertical: true,
1878
+ anchor,
1879
+ ja: { x, y: jaY, baseline: "middle" },
1880
+ en: enY === void 0 ? void 0 : { x, y: enY, baseline: "middle" }
1881
+ };
1882
+ placement.score = labelPlacementScore(placement, edge, laid, occupiedLabels) + (side === "right" ? 0 : 8) + offsetPenalty;
1883
+ return placement;
1884
+ }
1885
+ function labelPlacementScore(placement, edge, laid, occupiedLabels) {
1886
+ let score = placement.vertical ? 5 : 0;
1887
+ const box = placement.box;
1888
+ if (box.x < 0) score += Math.abs(box.x) * 300;
1889
+ if (box.y < 0) score += Math.abs(box.y) * 300;
1890
+ if (box.x + box.w > laid.width) score += (box.x + box.w - laid.width) * 300;
1891
+ if (box.y + box.h > laid.height) score += (box.y + box.h - laid.height) * 300;
1892
+ for (const node of laid.nodes) {
1893
+ if (node.isContainer) {
1894
+ const title = { x: node.x, y: node.y, w: node.w, h: 6 };
1895
+ score += rectOverlapArea(box, title) * 900;
1896
+ score += rectBorderOverlapPenalty(box, node) * 1800;
1897
+ score += rectBorderBandOverlapArea(box, node, 1.8) * 9e3;
1898
+ } else {
1899
+ const bodyOverlap = rectOverlapArea(box, node);
1900
+ if (bodyOverlap > 0) score += 1e6 + bodyOverlap * 5e4;
1901
+ score += rectOverlapArea(box, expandBox(node, 1.8)) * 5200;
1902
+ }
1903
+ }
1904
+ for (const other of laid.edges) {
1905
+ for (let i = 0; i < other.points.length - 1; i++) {
1906
+ const a = other.points[i];
1907
+ const b = other.points[i + 1];
1908
+ const overlap = segmentIntersectsRect(a, b, expandBox(box, 0.8));
1909
+ if (!overlap) continue;
1910
+ score += other === edge ? 35 : 380;
1911
+ }
1912
+ }
1913
+ for (const occupied of occupiedLabels) {
1914
+ score += rectOverlapArea(box, occupied) * 18e3;
1915
+ }
1916
+ const segmentLen = Math.abs(placement.lineA[0] - placement.lineB[0]) + Math.abs(placement.lineA[1] - placement.lineB[1]);
1917
+ const labelSpan = placement.vertical ? box.h : box.w;
1918
+ score += Math.max(0, Math.max(36, labelSpan * 2.4) - segmentLen) * 80;
1919
+ const center = placement.vertical ? box.y + box.h / 2 : box.x + box.w / 2;
1920
+ const start = placement.vertical ? placement.lineA[1] : placement.lineA[0];
1921
+ const end = placement.vertical ? placement.lineB[1] : placement.lineB[0];
1922
+ const endpointDistance = Math.min(Math.abs(center - start), Math.abs(center - end));
1923
+ score += Math.max(0, 12 - endpointDistance) * 80;
1924
+ return score;
1925
+ }
1926
+ function estimateTextWidth(text, fontSize) {
1927
+ let width = 0;
1928
+ for (const char of text) {
1929
+ if (char === " ") width += fontSize * 0.35;
1930
+ else if (char.charCodeAt(0) <= 127) width += fontSize * 0.58;
1931
+ else width += fontSize;
1932
+ }
1933
+ return Math.max(fontSize * 2, width);
1934
+ }
1935
+ function longestSegment(pts) {
1936
+ let best = 0;
1937
+ let bestLen = -1;
1938
+ for (let i = 0; i < pts.length - 1; i++) {
1939
+ const dx = pts[i + 1][0] - pts[i][0];
1940
+ const dy = pts[i + 1][1] - pts[i][1];
1941
+ const len = dx * dx + dy * dy;
1942
+ if (len > bestLen) {
1943
+ bestLen = len;
1944
+ best = i;
1945
+ }
1946
+ }
1947
+ const a = pts[best];
1948
+ const b = pts[best + 1];
1949
+ return [a, b];
1950
+ }
1951
+ function expandBox(box, pad) {
1952
+ return { x: box.x - pad, y: box.y - pad, w: box.w + pad * 2, h: box.h + pad * 2 };
1953
+ }
1954
+ function rectOverlapArea(a, b) {
1955
+ const x = Math.max(0, Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x));
1956
+ const y = Math.max(0, Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y));
1957
+ return x * y;
1958
+ }
1959
+ function rectBorderOverlapPenalty(a, b) {
1960
+ const nearLeft = Math.abs(a.x - b.x) < 1 || Math.abs(a.x + a.w - b.x) < 1;
1961
+ const nearRight = Math.abs(a.x - (b.x + b.w)) < 1 || Math.abs(a.x + a.w - (b.x + b.w)) < 1;
1962
+ const nearTop = Math.abs(a.y - b.y) < 1 || Math.abs(a.y + a.h - b.y) < 1;
1963
+ const nearBottom = Math.abs(a.y - (b.y + b.h)) < 1 || Math.abs(a.y + a.h - (b.y + b.h)) < 1;
1964
+ return Number(nearLeft || nearRight || nearTop || nearBottom);
1965
+ }
1966
+ function rectBorderBandOverlapArea(a, b, band) {
1967
+ return rectOverlapArea(a, { x: b.x - band, y: b.y - band, w: band * 2, h: b.h + band * 2 }) + rectOverlapArea(a, { x: b.x + b.w - band, y: b.y - band, w: band * 2, h: b.h + band * 2 }) + rectOverlapArea(a, { x: b.x - band, y: b.y - band, w: b.w + band * 2, h: band * 2 }) + rectOverlapArea(a, { x: b.x - band, y: b.y + b.h - band, w: b.w + band * 2, h: band * 2 });
1968
+ }
1969
+ function segmentIntersectsRect(a, b, box) {
1970
+ const left = box.x;
1971
+ const right = box.x + box.w;
1972
+ const top = box.y;
1973
+ const bottom = box.y + box.h;
1974
+ if (Math.abs(a[0] - b[0]) < EPS2) {
1975
+ const x = a[0];
1976
+ if (x <= left || x >= right) return false;
1977
+ return intervalOverlap2(a[1], b[1], top, bottom) > EPS2;
1978
+ }
1979
+ if (Math.abs(a[1] - b[1]) < EPS2) {
1980
+ const y = a[1];
1981
+ if (y <= top || y >= bottom) return false;
1982
+ return intervalOverlap2(a[0], b[0], left, right) > EPS2;
1983
+ }
1984
+ return false;
1985
+ }
1986
+ function intervalOverlap2(a1, a2, b1, b2) {
1987
+ const minA = Math.min(a1, a2);
1988
+ const maxA = Math.max(a1, a2);
1989
+ const minB = Math.min(b1, b2);
1990
+ const maxB = Math.max(b1, b2);
1991
+ return Math.max(0, Math.min(maxA, maxB) - Math.max(minA, minB));
1992
+ }
1993
+
1994
+ // src/core/refs.ts
1995
+ function refsToMarkdown(doc) {
1996
+ const ids = sortedIds(doc);
1997
+ const lines = [];
1998
+ lines.push("## \u7B26\u53F7\u306E\u8AAC\u660E / Reference Signs");
1999
+ lines.push("");
2000
+ lines.push("| \u7B26\u53F7 | \u540D\u79F0(\u65E5\u672C\u8A9E) | Name (English) |");
2001
+ lines.push("|------|---------------|----------------|");
2002
+ for (const id of ids) {
2003
+ const n = doc.nodes.get(id);
2004
+ lines.push(`| ${id} | ${n.label.ja ?? ""} | ${n.label.en ?? ""} |`);
2005
+ }
2006
+ return lines.join("\n");
2007
+ }
2008
+ function refsToCsv(doc) {
2009
+ const ids = sortedIds(doc);
2010
+ const lines = ["id,ja,en"];
2011
+ for (const id of ids) {
2012
+ const n = doc.nodes.get(id);
2013
+ lines.push(`${csv(id)},${csv(n.label.ja ?? "")},${csv(n.label.en ?? "")}`);
2014
+ }
2015
+ return lines.join("\n");
2016
+ }
2017
+ function csv(s) {
2018
+ return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
2019
+ }
2020
+ function sortedIds(doc) {
2021
+ const ids = [...doc.nodes.keys()].filter((id) => id !== "*");
2022
+ return ids.sort((a, b) => {
2023
+ const na = parseInt(a, 10);
2024
+ const nb = parseInt(b, 10);
2025
+ if (!isNaN(na) && !isNaN(nb)) return na - nb;
2026
+ return a.localeCompare(b);
2027
+ });
2028
+ }
2029
+
2030
+ // src/core/samples.ts
2031
+ var SAMPLE_ORDER = [
2032
+ "block",
2033
+ "system",
2034
+ "iot",
2035
+ "imagePipeline",
2036
+ "controlLoop",
2037
+ "flow",
2038
+ "state",
2039
+ "seq",
2040
+ "handshake"
2041
+ ];
2042
+ var SAMPLES = {
2043
+ block: {
2044
+ label: "\u30D6\u30ED\u30C3\u30AF\u56F3",
2045
+ hint: "\u300C:\u300D\u3092\u4F7F\u3046\u3068\u30D6\u30ED\u30C3\u30AF\u56F3\u306B\u306A\u308B",
2046
+ source: `# \u30D6\u30ED\u30C3\u30AF\u56F3(\u88C5\u7F6E\u30AF\u30EC\u30FC\u30E0\u7528)
2047
+ # \u30D2\u30F3\u30C8:\u300C:\u300D\u3067\u5305\u542B\u95A2\u4FC2\u3092\u66F8\u304F \u2192 \u30D6\u30ED\u30C3\u30AF\u56F3\u3068\u5224\u5B9A\u3055\u308C\u308B
2048
+
2049
+ 10 = \u5236\u5FA1\u88C5\u7F6E / control device
2050
+ 11 = CPU
2051
+ 12 = \u30E1\u30E2\u30EA / memory
2052
+ 13 = "I/O \u30A4\u30F3\u30BF\u30FC\u30D5\u30A7\u30FC\u30B9" / "I/O interface"
2053
+ 20 = \u5916\u90E8\u6A5F\u5668 / external device
2054
+
2055
+ 10 : 11 12 13
2056
+
2057
+ 11 - 12
2058
+ 11 - 13
2059
+ 13 -> 20 : \u4FE1\u53F7 / signal
2060
+ `
2061
+ },
2062
+ system: {
2063
+ label: "\u30B7\u30B9\u30C6\u30E0\u5168\u4F53",
2064
+ hint: "\u968E\u5C64\u69CB\u6210 + \u5916\u90E8\u63A5\u7D9A",
2065
+ source: `# \u30B7\u30B9\u30C6\u30E0\u5168\u4F53(\u968E\u5C64+\u5916\u90E8\u63A5\u7D9A)
2066
+ # \u30D2\u30F3\u30C8: \u5185\u90E8\u30D6\u30ED\u30C3\u30AF\u3068\u5916\u90E8\u88C5\u7F6E\u3092\u5206\u3051\u3066\u66F8\u304F\u3068\u3001\u7279\u8A31\u56F3\u9762\u3089\u3057\u3044\u69CB\u6210\u306B\u306A\u308B
2067
+
2068
+ 100 = \u30B7\u30B9\u30C6\u30E0\u672C\u4F53 / Main system
2069
+ 10 = \u5236\u5FA1\u90E8 / Control
2070
+ 20 = \u901A\u4FE1\u90E8 / Comm
2071
+ 11 = CPU
2072
+ 12 = \u30E1\u30E2\u30EA / memory
2073
+ 21 = \u7121\u7DDA\u90E8 / wireless
2074
+ 22 = \u6709\u7DDA\u90E8 / wired
2075
+ 30 = \u5916\u90E8\u30B5\u30FC\u30D0 / external server
2076
+ 40 = \u5916\u90E8\u7AEF\u672B / external terminal
2077
+
2078
+ 100 : 10 20
2079
+ 10 : 11 12
2080
+ 20 : 21 22
2081
+
2082
+ 21 .> 40 : \u7121\u7DDA / wireless
2083
+ 22 -> 30 : \u6709\u7DDA / wired
2084
+ 30 <-> 40 : \u901A\u4FE1 / comm
2085
+ `
2086
+ },
2087
+ iot: {
2088
+ label: "IoT/\u30AF\u30E9\u30A6\u30C9",
2089
+ hint: "\u30BB\u30F3\u30B5\u7AEF\u672B\u30FB\u30B2\u30FC\u30C8\u30A6\u30A7\u30A4\u30FB\u30AF\u30E9\u30A6\u30C9\u306E\u5178\u578B\u69CB\u6210",
2090
+ source: `# IoT/\u30AF\u30E9\u30A6\u30C9\u69CB\u6210
2091
+ # \u30D2\u30F3\u30C8: \u7AEF\u672B\u5185\u90E8\u306E\u69CB\u6210\u3068\u30AF\u30E9\u30A6\u30C9\u5074\u306E\u69CB\u6210\u3092\u5225\u30B3\u30F3\u30C6\u30CA\u306B\u3059\u308B
2092
+
2093
+ 100 = \u30BB\u30F3\u30B5\u7AEF\u672B / sensor terminal
2094
+ 10 = \u691C\u51FA\u90E8 / detector
2095
+ 11 = \u6E29\u5EA6\u30BB\u30F3\u30B5 / temperature sensor
2096
+ 12 = \u52A0\u901F\u5EA6\u30BB\u30F3\u30B5 / acceleration sensor
2097
+ 20 = \u51E6\u7406\u90E8 / processor
2098
+ 21 = \u53D6\u5F97\u90E8 / acquisition unit
2099
+ 22 = \u5224\u5B9A\u90E8 / determination unit
2100
+ 30 = \u901A\u4FE1\u90E8 / communication unit
2101
+ 200 = \u30B2\u30FC\u30C8\u30A6\u30A7\u30A4 / gateway
2102
+ 300 = \u30AF\u30E9\u30A6\u30C9\u30B5\u30FC\u30D0 / cloud server
2103
+ 310 = \u53D7\u4FE1\u90E8 / receiver
2104
+ 320 = \u89E3\u6790\u90E8 / analyzer
2105
+ 330 = \u8A18\u61B6\u90E8 / storage
2106
+
2107
+ 100 : 10 20 30
2108
+ 10 : 11 12
2109
+ 20 : 21 22
2110
+ 300 : 310 320 330
2111
+
2112
+ 10 -> 20 : \u30BB\u30F3\u30B5\u5024 / sensor value
2113
+ 20 -> 30 : \u9001\u4FE1\u30C7\u30FC\u30BF / transmission data
2114
+ 30 .> 200 : \u7121\u7DDA / wireless
2115
+ 200 -> 310 : \u4E2D\u7D99 / relay
2116
+ 310 -> 320 : \u30C7\u30FC\u30BF / data
2117
+ 320 -> 330 : \u7D50\u679C / result
2118
+ `
2119
+ },
2120
+ imagePipeline: {
2121
+ label: "\u753B\u50CF\u51E6\u7406",
2122
+ hint: "\u753B\u50CF\u5165\u529B\u304B\u3089\u5224\u5B9A\u7D50\u679C\u307E\u3067\u306E\u51E6\u7406\u30D1\u30A4\u30D7\u30E9\u30A4\u30F3",
2123
+ source: `# \u753B\u50CF\u51E6\u7406\u30D1\u30A4\u30D7\u30E9\u30A4\u30F3(\u65B9\u6CD5\u30AF\u30EC\u30FC\u30E0\u7528)
2124
+ # \u30D2\u30F3\u30C8: \u7247\u65B9\u5411\u306E\u63A5\u7D9A\u306E\u307F\u306A\u3089\u30D5\u30ED\u30FC\u30C1\u30E3\u30FC\u30C8\u3068\u3057\u3066\u63CF\u753B\u3055\u308C\u308B
2125
+
2126
+ S100 = \u753B\u50CF\u3092\u53D6\u5F97 / Acquire image
2127
+ S110 = \u524D\u51E6\u7406 / Preprocess
2128
+ S120 = \u7279\u5FB4\u91CF\u3092\u62BD\u51FA / Extract features
2129
+ S130 = \u6B20\u9665\u3042\u308A? / Defect?
2130
+ S140 = \u30A2\u30E9\u30FC\u30C8\u3092\u51FA\u529B / Output alert
2131
+ S150 = \u6B63\u5E38\u7D50\u679C\u3092\u8A18\u9332 / Record normal result
2132
+ S160 = \u7D42\u4E86 / End
2133
+
2134
+ S100 -> S110
2135
+ S110 -> S120
2136
+ S120 -> S130
2137
+ S130 -> S140 : Yes
2138
+ S130 -> S150 : No
2139
+ S140 -> S160
2140
+ S150 -> S160
2141
+ `
2142
+ },
2143
+ controlLoop: {
2144
+ label: "\u5236\u5FA1\u30EB\u30FC\u30D7",
2145
+ hint: "\u30BB\u30F3\u30B5\u30FB\u5236\u5FA1\u5668\u30FB\u30A2\u30AF\u30C1\u30E5\u30A8\u30FC\u30BF\u306E\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF\u69CB\u6210",
2146
+ source: `# \u5236\u5FA1\u30EB\u30FC\u30D7(\u88C5\u7F6E\u30AF\u30EC\u30FC\u30E0\u7528)
2147
+ # \u30D2\u30F3\u30C8: \u30EB\u30FC\u30D7\u3059\u308B\u69CB\u6210\u306F\u30011\u3064\u306E\u30B7\u30B9\u30C6\u30E0\u5185\u3067\u6642\u8A08\u56DE\u308A\u306B\u4E26\u3079\u308B\u3068\u8AAD\u307F\u3084\u3059\u3044
2148
+
2149
+ 100 = \u5236\u5FA1\u30B7\u30B9\u30C6\u30E0 / control system
2150
+ 10 = \u5236\u5FA1\u90E8 / controller
2151
+ 11 = \u76EE\u6A19\u5024\u53D6\u5F97\u90E8 / target acquisition unit
2152
+ 12 = \u504F\u5DEE\u7B97\u51FA\u90E8 / error calculator
2153
+ 13 = \u6307\u4EE4\u751F\u6210\u90E8 / command generator
2154
+ 20 = \u99C6\u52D5\u90E8 / driver
2155
+ 30 = \u30BB\u30F3\u30B5\u90E8 / sensor unit
2156
+ 40 = \u5BFE\u8C61\u88C5\u7F6E / controlled object
2157
+
2158
+ 100 : 30 10 40 20
2159
+ 10 : 11 12 13
2160
+
2161
+ 30 -> 12 : \u6E2C\u5B9A\u5024 / measured value
2162
+ 11 -> 12 : \u76EE\u6A19\u5024 / target value
2163
+ 12 -> 13 : \u504F\u5DEE / error
2164
+ 13 -> 20 : \u6307\u4EE4 / command
2165
+ 20 -> 40 : \u99C6\u52D5\u4FE1\u53F7 / drive signal
2166
+ 40 .> 30 : \u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF / feedback
2167
+ `
2168
+ },
2169
+ flow: {
2170
+ label: "\u30D5\u30ED\u30FC\u30C1\u30E3\u30FC\u30C8",
2171
+ hint: "\u30E9\u30D9\u30EB\u306B\u300C?\u300D\u304C\u3042\u308B \u2192 \u30D5\u30ED\u30FC\u3068\u5224\u5B9A",
2172
+ source: `# \u30D5\u30ED\u30FC\u30C1\u30E3\u30FC\u30C8(\u65B9\u6CD5\u30AF\u30EC\u30FC\u30E0\u7528)
2173
+ # \u30D2\u30F3\u30C8:\u30E9\u30D9\u30EB\u672B\u5C3E\u306B\u300C?\u300D \u2192 \u83F1\u5F62(\u6761\u4EF6\u5206\u5C90)\u306B\u81EA\u52D5\u63A8\u8AD6
2174
+
2175
+ S100 = \u958B\u59CB / Start
2176
+ S110 = \u6761\u4EF6A? / "Condition A?"
2177
+ S120 = \u51E6\u7406X / Process X
2178
+ S130 = \u51E6\u7406Y / Process Y
2179
+ S140 = \u7D42\u4E86 / End
2180
+
2181
+ S100 -> S110
2182
+ S110 -> S120 : Yes
2183
+ S110 -> S130 : No
2184
+ S120 -> S140
2185
+ S130 -> S140
2186
+ `
2187
+ },
2188
+ state: {
2189
+ label: "\u72B6\u614B\u9077\u79FB\u56F3",
2190
+ hint: "\u300C*\u300D\u3092\u4F7F\u3046 \u2192 \u72B6\u614B\u9077\u79FB\u3068\u5224\u5B9A",
2191
+ source: `# \u72B6\u614B\u9077\u79FB\u56F3
2192
+ # \u30D2\u30F3\u30C8:\u300C*\u300D\u304C\u521D\u671F/\u7D42\u7AEF\u306E\u7B26\u53F7(\u9ED2\u4E38\u3067\u63CF\u753B)
2193
+
2194
+ S1 = \u5F85\u6A5F / Idle
2195
+ S2 = \u52D5\u4F5C\u4E2D / Running
2196
+ S3 = \u30A8\u30E9\u30FC / Error
2197
+
2198
+ * -> S1
2199
+ S1 -> S2 : \u8D77\u52D5 / start
2200
+ S2 -> S1 : \u505C\u6B62 / stop
2201
+ S2 -> S3 : \u7570\u5E38 / fault
2202
+ S3 -> S1 : \u30EA\u30BB\u30C3\u30C8 / reset
2203
+ S3 -> *
2204
+ `
2205
+ },
2206
+ seq: {
2207
+ label: "\u30B7\u30FC\u30B1\u30F3\u30B9\u56F3",
2208
+ hint: "\u300C:\u300D\u3082\u300C?\u300D\u3082\u300C*\u300D\u3082\u306A\u3044 \u2192 \u30B7\u30FC\u30B1\u30F3\u30B9",
2209
+ source: `# \u30B7\u30FC\u30B1\u30F3\u30B9\u56F3(\u30D7\u30ED\u30C8\u30B3\u30EB\u7CFB)
2210
+ # \u30D2\u30F3\u30C8:\u5305\u542B\u3082?\u3082*\u3082\u7121\u3044 \u2192 \u30B7\u30FC\u30B1\u30F3\u30B9\u56F3\u3068\u3057\u3066\u63CF\u753B
2211
+ # \u30A2\u30AF\u30BF\u306F\u767B\u5834\u9806\u306B\u5DE6\u304B\u3089\u4E26\u3076
2212
+
2213
+ 100 = \u30AF\u30E9\u30A4\u30A2\u30F3\u30C8 / client
2214
+ 200 = \u30B5\u30FC\u30D0 / server
2215
+
2216
+ 100 -> 200 : \u8A8D\u8A3C\u8981\u6C42 / auth request
2217
+ 200 -> 100 : \u30C8\u30FC\u30AF\u30F3 / token
2218
+ 100 -> 200 : \u30EA\u30BD\u30FC\u30B9\u8981\u6C42 / resource request
2219
+ 200 -> 100 : \u30EA\u30BD\u30FC\u30B9\u5FDC\u7B54 / resource response
2220
+ `
2221
+ },
2222
+ handshake: {
2223
+ label: "\u30CF\u30F3\u30C9\u30B7\u30A7\u30A4\u30AF",
2224
+ hint: "\u8981\u6C42/\u5FDC\u7B54/\u78BA\u7ACB/\u7D42\u4E86\u306E\u901A\u4FE1\u30B7\u30FC\u30B1\u30F3\u30B9",
2225
+ source: `# \u901A\u4FE1\u30CF\u30F3\u30C9\u30B7\u30A7\u30A4\u30AF(\u30B7\u30FC\u30B1\u30F3\u30B9\u56F3)
2226
+ # \u30D2\u30F3\u30C8: \u5F80\u5FA9\u30E1\u30C3\u30BB\u30FC\u30B8\u304C\u3042\u308B\u3068\u30B7\u30FC\u30B1\u30F3\u30B9\u56F3\u306B\u306A\u308B
2227
+
2228
+ 100 = \u7AEF\u672B / terminal
2229
+ 200 = \u30B5\u30FC\u30D0 / server
2230
+
2231
+ 100 -> 200 : \u63A5\u7D9A\u8981\u6C42 / connect request
2232
+ 200 -> 100 : \u5FDC\u7B54 / response
2233
+ 100 -> 200 : \u8A8D\u8A3C\u60C5\u5831 / credentials
2234
+ 200 -> 100 : \u8A8D\u8A3C\u7D50\u679C / auth result
2235
+ 100 <-> 200 : \u30C7\u30FC\u30BF\u901A\u4FE1 / data exchange
2236
+ 100 -> 200 : \u5207\u65AD\u8981\u6C42 / disconnect
2237
+ 200 -> 100 : \u5207\u65AD\u5B8C\u4E86 / disconnected
2238
+ `
2239
+ }
2240
+ };
2241
+
2242
+ // src/core/patterns.ts
2243
+ var PATTERN_LABEL = {
2244
+ cond: "\u6761\u4EF6\u5206\u5C90",
2245
+ container: "\u89AA\u5B50\u69CB\u6210",
2246
+ external: "\u89AA\u5B50+\u5916\u90E8",
2247
+ seq: "\u30B7\u30FC\u30B1\u30F3\u30B9",
2248
+ state: "\u72B6\u614B\u9077\u79FB",
2249
+ bidir: "\u53CC\u65B9\u5411\u901A\u4FE1",
2250
+ hierarchy: "3\u968E\u5C64\u69CB\u6210",
2251
+ pipeline: "N\u6BB5\u968E\u51E6\u7406",
2252
+ parallel: "\u4E26\u5217\u51E6\u7406",
2253
+ handshake: "\u901A\u4FE1\u30CF\u30F3\u30C9\u30B7\u30A7\u30A4\u30AF",
2254
+ state_with_cond: "\u72B6\u614B\u5224\u5B9A\u30D5\u30ED\u30FC",
2255
+ system: "\u30B7\u30B9\u30C6\u30E0\u5168\u4F53"
2256
+ };
2257
+ var PATTERN_SOURCE = {
2258
+ cond: `S100 = \u958B\u59CB / Start
2259
+ S110 = \u5224\u5B9A? / Decision?
2260
+ S120 = Yes\u51E6\u7406 / Yes Process
2261
+ S130 = No\u51E6\u7406 / No Process
2262
+ S140 = \u7D42\u4E86 / End
2263
+ S100 -> S110
2264
+ S110 -> S120 : Yes
2265
+ S110 -> S130 : No
2266
+ S120 -> S140
2267
+ S130 -> S140`,
2268
+ container: `10 = \u89AA\u88C5\u7F6E / Parent
2269
+ 11 = \u90E8\u54C11 / Part 1
2270
+ 12 = \u90E8\u54C12 / Part 2
2271
+ 13 = \u90E8\u54C13 / Part 3
2272
+ 10 : 11 12 13`,
2273
+ external: `10 = \u5236\u5FA1\u88C5\u7F6E / control device
2274
+ 11 = \u5165\u529B\u90E8 / input
2275
+ 12 = \u51E6\u7406\u90E8 / processor
2276
+ 20 = \u5916\u90E8\u6A5F\u5668 / external device
2277
+ 10 : 11 12
2278
+ 12 -> 20 : \u4FE1\u53F7 / signal`,
2279
+ seq: `100 = \u30AF\u30E9\u30A4\u30A2\u30F3\u30C8 / client
2280
+ 200 = \u30B5\u30FC\u30D0 / server
2281
+ 100 -> 200 : \u8981\u6C42 / request
2282
+ 200 -> 100 : \u5FDC\u7B54 / response
2283
+ 100 -> 200 : \u5207\u65AD / disconnect`,
2284
+ state: `S1 = \u5F85\u6A5F / Idle
2285
+ S2 = \u52D5\u4F5C\u4E2D / Running
2286
+ * -> S1
2287
+ S1 -> S2 : \u8D77\u52D5 / start
2288
+ S2 -> S1 : \u505C\u6B62 / stop`,
2289
+ bidir: `100 = \u7AEF\u672BA / Terminal A
2290
+ 200 = \u7AEF\u672BB / Terminal B
2291
+ 100 <-> 200 : \u901A\u4FE1 / comm`,
2292
+ hierarchy: `100 = \u30B7\u30B9\u30C6\u30E0 / System
2293
+ 10 = \u30B5\u30D6\u30B7\u30B9\u30C6\u30E0A / Subsystem A
2294
+ 20 = \u30B5\u30D6\u30B7\u30B9\u30C6\u30E0B / Subsystem B
2295
+ 11 = \u90E8\u54C1A1 / Part A1
2296
+ 12 = \u90E8\u54C1A2 / Part A2
2297
+ 21 = \u90E8\u54C1B1 / Part B1
2298
+ 22 = \u90E8\u54C1B2 / Part B2
2299
+ 100 : 10 20
2300
+ 10 : 11 12
2301
+ 20 : 21 22`,
2302
+ pipeline: `S100 = \u5165\u529B / Input
2303
+ S110 = \u524D\u51E6\u7406 / Preprocess
2304
+ S120 = \u4E3B\u51E6\u7406 / Main Process
2305
+ S130 = \u5F8C\u51E6\u7406 / Postprocess
2306
+ S140 = \u51FA\u529B / Output
2307
+ S100 -> S110
2308
+ S110 -> S120
2309
+ S120 -> S130
2310
+ S130 -> S140`,
2311
+ parallel: `S100 = \u958B\u59CB / Start
2312
+ S110 = \u5206\u5C90 / Branch
2313
+ S120 = \u7D4C\u8DEFA / Path A
2314
+ S130 = \u7D4C\u8DEFB / Path B
2315
+ S140 = \u7D4C\u8DEFC / Path C
2316
+ S150 = \u5408\u6D41 / Join
2317
+ S160 = \u7D42\u4E86 / End
2318
+ S100 -> S110
2319
+ S110 -> S120
2320
+ S110 -> S130
2321
+ S110 -> S140
2322
+ S120 -> S150
2323
+ S130 -> S150
2324
+ S140 -> S150
2325
+ S150 -> S160`,
2326
+ handshake: `100 = \u7AEF\u672B / Terminal
2327
+ 200 = \u30B5\u30FC\u30D0 / Server
2328
+ 100 -> 200 : SYN\u8981\u6C42 / SYN
2329
+ 200 -> 100 : SYN+ACK / SYN+ACK
2330
+ 100 -> 200 : ACK\u5FDC\u7B54 / ACK
2331
+ 100 <-> 200 : \u30C7\u30FC\u30BF\u4EA4\u63DB / data exchange
2332
+ 100 -> 200 : FIN / FIN
2333
+ 200 -> 100 : ACK / ACK`,
2334
+ state_with_cond: `S100 = \u5F85\u6A5F\u72B6\u614B / Idle state
2335
+ S110 = \u691C\u8A3C\u51E6\u7406 / Verify
2336
+ S120 = OK? / OK?
2337
+ S130 = \u5B8C\u4E86\u72B6\u614B / Done state
2338
+ S140 = \u5931\u6557\u72B6\u614B / Failed state
2339
+ S150 = \u518D\u8A66\u884C / Retry
2340
+ S100 -> S110 : \u8981\u6C42 / request
2341
+ S110 -> S120 : \u5224\u5B9A / check
2342
+ S120 -> S130 : OK
2343
+ S120 -> S140 : NG
2344
+ S140 -> S150
2345
+ S150 -> S100`,
2346
+ system: `100 = \u30B7\u30B9\u30C6\u30E0\u672C\u4F53 / Main system
2347
+ 10 = \u5236\u5FA1\u90E8 / Control
2348
+ 20 = \u901A\u4FE1\u90E8 / Comm
2349
+ 11 = CPU
2350
+ 12 = \u30E1\u30E2\u30EA / memory
2351
+ 21 = \u7121\u7DDA\u90E8 / wireless
2352
+ 22 = \u6709\u7DDA\u90E8 / wired
2353
+ 30 = \u5916\u90E8\u30B5\u30FC\u30D0 / external server
2354
+ 40 = \u5916\u90E8\u7AEF\u672B / external terminal
2355
+ 100 : 10 20
2356
+ 10 : 11 12
2357
+ 20 : 21 22
2358
+ 21 .> 40 : \u7121\u7DDA / wireless
2359
+ 22 -> 30 : \u6709\u7DDA / wired
2360
+ 30 <-> 40 : \u901A\u4FE1 / comm`
2361
+ };
2362
+ // Annotate the CommonJS export names for ESM import in node:
2363
+ 0 && (module.exports = {
2364
+ OP_TABLE,
2365
+ PATTERN_LABEL,
2366
+ PATTERN_SOURCE,
2367
+ SAMPLES,
2368
+ SAMPLE_ORDER,
2369
+ chooseLabelPlacement,
2370
+ estimateTextWidth,
2371
+ layout,
2372
+ parse,
2373
+ refsToCsv,
2374
+ refsToMarkdown,
2375
+ render,
2376
+ splitBilingual,
2377
+ stripComment
2378
+ });