@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/pdgkit.js ADDED
@@ -0,0 +1,3534 @@
1
+ #!/usr/bin/env node
2
+
3
+ // bin/pdgkit.ts
4
+ import fs from "fs";
5
+
6
+ // src/core/parser.ts
7
+ var ID_PATTERN = /[A-Za-z0-9_*]+/.source;
8
+ var OP_TABLE = [
9
+ { lit: "<->", kind: "bidir" },
10
+ { lit: "=>", kind: "thick" },
11
+ { lit: "->", kind: "arrow" },
12
+ { lit: "<-", kind: "arrow", reverse: true },
13
+ { lit: ".>", kind: "dashed-arrow" },
14
+ { lit: "..", kind: "dashed" },
15
+ { lit: "-", kind: "line" }
16
+ ];
17
+ var DEF_RE = new RegExp(`^(${ID_PATTERN})\\s*=(?!>)\\s*(.*)$`);
18
+ var CONN_RE = new RegExp(
19
+ `^(${ID_PATTERN})\\s+(<->|=>|->|<-|\\.>|\\.\\.|-)\\s+(${ID_PATTERN})\\s*(?::\\s*(.*))?$`
20
+ );
21
+ var CONT_RE = new RegExp(`^(${ID_PATTERN})\\s*:\\s*(.+)$`);
22
+ function parse(source) {
23
+ const doc = {
24
+ nodes: /* @__PURE__ */ new Map(),
25
+ containments: [],
26
+ edges: [],
27
+ diagnostics: [],
28
+ kind: "block"
29
+ };
30
+ const lines = source.split(/\r?\n/);
31
+ for (let i = 0; i < lines.length; i++) {
32
+ const raw = stripComment(lines[i]);
33
+ const line = raw.trim();
34
+ if (!line) continue;
35
+ if (handleDef(line, i + 1, doc)) continue;
36
+ if (handleConn(line, i + 1, doc)) continue;
37
+ if (handleCont(line, i + 1, doc)) continue;
38
+ doc.diagnostics.push({
39
+ severity: "error",
40
+ line: i + 1,
41
+ col: 1,
42
+ message: `\u69CB\u6587\u4E0D\u660E: "${line}"`
43
+ });
44
+ }
45
+ doc.kind = inferKind(doc);
46
+ return doc;
47
+ }
48
+ function handleDef(line, lineNum, doc) {
49
+ const m = line.match(DEF_RE);
50
+ if (!m) return false;
51
+ const id = m[1];
52
+ const tail = m[2];
53
+ const label = splitBilingual(tail);
54
+ const existing = doc.nodes.get(id);
55
+ if (existing) {
56
+ doc.diagnostics.push({
57
+ severity: "warning",
58
+ line: lineNum,
59
+ col: 1,
60
+ message: `\u7B26\u53F7 "${id}" \u306F\u518D\u5B9A\u7FA9\u3055\u308C\u307E\u3057\u305F`
61
+ });
62
+ }
63
+ doc.nodes.set(id, {
64
+ id,
65
+ label,
66
+ implicit: false
67
+ });
68
+ return true;
69
+ }
70
+ function handleConn(line, lineNum, doc) {
71
+ const m = line.match(CONN_RE);
72
+ if (!m) return false;
73
+ const opLit = m[2];
74
+ const entry = OP_TABLE.find((o) => o.lit === opLit);
75
+ const from = entry.reverse ? m[3] : m[1];
76
+ const to = entry.reverse ? m[1] : m[3];
77
+ const labelText = m[4] ?? "";
78
+ const edge = {
79
+ from,
80
+ to,
81
+ op: entry.kind,
82
+ label: labelText.trim() ? splitBilingual(labelText) : void 0,
83
+ line: lineNum
84
+ };
85
+ doc.edges.push(edge);
86
+ ensureNode(doc, from);
87
+ ensureNode(doc, to);
88
+ return true;
89
+ }
90
+ function handleCont(line, lineNum, doc) {
91
+ const m = line.match(CONT_RE);
92
+ if (!m) return false;
93
+ const parent = m[1];
94
+ const rest = m[2].trim();
95
+ const children = rest.split(/\s+/).filter(Boolean);
96
+ const idRe = new RegExp(`^${ID_PATTERN}$`);
97
+ for (const c of children) {
98
+ if (!idRe.test(c)) {
99
+ doc.diagnostics.push({
100
+ severity: "error",
101
+ line: lineNum,
102
+ col: 1,
103
+ message: `\u5305\u542B\u306E\u5B50\u3068\u3057\u3066\u4E0D\u6B63\u306A\u30C8\u30FC\u30AF\u30F3: "${c}"`
104
+ });
105
+ return true;
106
+ }
107
+ }
108
+ doc.containments.push({ parent, children, line: lineNum });
109
+ ensureNode(doc, parent);
110
+ for (const c of children) ensureNode(doc, c);
111
+ return true;
112
+ }
113
+ function ensureNode(doc, id) {
114
+ let n = doc.nodes.get(id);
115
+ if (n) return n;
116
+ n = { id, label: {}, implicit: true };
117
+ doc.nodes.set(id, n);
118
+ return n;
119
+ }
120
+ function stripComment(line) {
121
+ let inQuote = false;
122
+ for (let i = 0; i < line.length; i++) {
123
+ const c = line[i];
124
+ if (c === '"') inQuote = !inQuote;
125
+ else if (!inQuote && c === "#") return line.slice(0, i);
126
+ }
127
+ return line;
128
+ }
129
+ function splitBilingual(text) {
130
+ const s = text.trim();
131
+ if (!s) return {};
132
+ const slashIdx = findBilingualSeparator(s);
133
+ if (slashIdx === -1) {
134
+ return { ja: stripQuotes(s) };
135
+ }
136
+ return {
137
+ ja: stripQuotes(s.slice(0, slashIdx).trim()),
138
+ en: stripQuotes(s.slice(slashIdx + 1).trim())
139
+ };
140
+ }
141
+ function findBilingualSeparator(s) {
142
+ let inQuote = false;
143
+ for (let i = 0; i < s.length; i++) {
144
+ if (s[i] === '"') {
145
+ inQuote = !inQuote;
146
+ continue;
147
+ }
148
+ if (!inQuote && s[i] === "/" && isSpace(s[i - 1]) && isSpace(s[i + 1])) return i;
149
+ }
150
+ return -1;
151
+ }
152
+ function isSpace(ch) {
153
+ return ch === " " || ch === " ";
154
+ }
155
+ function stripQuotes(s) {
156
+ if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
157
+ return s.slice(1, -1);
158
+ }
159
+ return s;
160
+ }
161
+ function inferKind(doc) {
162
+ if (doc.containments.length > 0) return "block";
163
+ for (const n of doc.nodes.values()) {
164
+ const ja = n.label.ja ?? "";
165
+ const en = n.label.en ?? "";
166
+ if (ja.endsWith("?") || en.endsWith("?")) return "flow";
167
+ }
168
+ if (doc.nodes.has("*")) return "state";
169
+ const pairs = /* @__PURE__ */ new Set();
170
+ for (const e of doc.edges) {
171
+ if (e.op === "bidir") return "seq";
172
+ const fwd = `${e.from}|${e.to}`;
173
+ const rev = `${e.to}|${e.from}`;
174
+ if (pairs.has(rev)) return "seq";
175
+ pairs.add(fwd);
176
+ }
177
+ return "flow";
178
+ }
179
+
180
+ // src/core/layout.ts
181
+ var NODE_W = 36;
182
+ var NODE_H = 14;
183
+ var PAD = 8;
184
+ var GRID_GAP = 24;
185
+ var TITLE_H = 5;
186
+ var PARENT_EDGE_LANE_H = 14;
187
+ var MARGIN = 8;
188
+ var ROOT_GAP = MARGIN * 4;
189
+ var ROUTE_GAP = PAD * 1.5;
190
+ var OBSTACLE_PAD = 0.8;
191
+ var BORDER_CLEARANCE = 4;
192
+ var ARROW_TERMINAL_CLEARANCE = 4.6;
193
+ var THICK_ARROW_TERMINAL_CLEARANCE = 5.4;
194
+ var VERTICAL_PORT_RATIO = 0.25;
195
+ var PORT_STUB = 6;
196
+ var MAX_ROUTE_LANES = 18;
197
+ var EPS = 1e-3;
198
+ function layout(doc) {
199
+ switch (doc.kind) {
200
+ case "block":
201
+ return layoutBlock(doc);
202
+ case "flow":
203
+ return layoutFlow(doc);
204
+ case "state":
205
+ return layoutState(doc);
206
+ case "seq":
207
+ return layoutSeq(doc);
208
+ }
209
+ }
210
+ function layoutBlock(doc) {
211
+ const childMap = /* @__PURE__ */ new Map();
212
+ for (const c of doc.containments) childMap.set(c.parent, c.children);
213
+ const parentMap = /* @__PURE__ */ new Map();
214
+ for (const c of doc.containments) {
215
+ for (const child of c.children) parentMap.set(child, c.parent);
216
+ }
217
+ const childIds = /* @__PURE__ */ new Set();
218
+ for (const cs of childMap.values()) for (const c of cs) childIds.add(c);
219
+ const allIds = [...doc.nodes.keys()];
220
+ const roots = allIds.filter((id) => !childIds.has(id));
221
+ const placed = [];
222
+ const positions = /* @__PURE__ */ new Map();
223
+ function size(id) {
224
+ const children = childMap.get(id);
225
+ if (!children || children.length === 0) return { w: NODE_W, h: NODE_H };
226
+ const sizes = children.map(size);
227
+ if (arrangementOf(id) === "grid") {
228
+ const cols = gridColsOf(id, children);
229
+ const colW = Math.max(...sizes.map((s) => s.w));
230
+ const rowHeights = gridRowHeights(sizes, cols);
231
+ return {
232
+ w: cols * colW + 2 * PAD + (cols - 1) * GRID_GAP,
233
+ h: titleHeightOf(id) + rowHeights.reduce((sum, h) => sum + h, 0) + 2 * PAD + Math.max(0, rowHeights.length - 1) * GRID_GAP
234
+ };
235
+ }
236
+ const maxW = Math.max(...sizes.map((s) => s.w));
237
+ const totH = sizes.reduce((a, b) => a + b.h, 0);
238
+ return {
239
+ w: maxW + 2 * PAD,
240
+ h: titleHeightOf(id) + totH + 2 * PAD + (children.length - 1) * GRID_GAP
241
+ };
242
+ }
243
+ function arrangementOf(id) {
244
+ const children = childMap.get(id);
245
+ if (!children || children.length <= 2) return "stack";
246
+ if (hasParentToChildEdges(id, children)) return "grid";
247
+ return hasLinearChildFlow(children, doc.edges, childMap) ? "stack" : "grid";
248
+ }
249
+ function gridColsOf(id, children) {
250
+ if (hasParentToChildEdges(id, children)) return Math.min(children.length, 4);
251
+ if (children.length >= 7) return 4;
252
+ if (children.length >= 5) return 3;
253
+ return 2;
254
+ }
255
+ function gridRowHeights(sizes, cols) {
256
+ const rows = Math.ceil(sizes.length / cols);
257
+ const heights = [];
258
+ for (let row = 0; row < rows; row++) {
259
+ const rowSizes = sizes.slice(row * cols, row * cols + cols);
260
+ heights.push(Math.max(...rowSizes.map((s) => s.h)));
261
+ }
262
+ return heights;
263
+ }
264
+ function titleHeightOf(id) {
265
+ const children = childMap.get(id);
266
+ return children && hasParentToChildEdges(id, children) ? TITLE_H + PARENT_EDGE_LANE_H : TITLE_H;
267
+ }
268
+ function hasParentToChildEdges(id, children) {
269
+ return doc.edges.some((edge) => edge.from === id && children.some((child) => child === edge.to || containsDescendant(child, edge.to, childMap)));
270
+ }
271
+ function place(id, ox, oy) {
272
+ const s = size(id);
273
+ positions.set(id, { x: ox, y: oy, w: s.w, h: s.h });
274
+ const node = doc.nodes.get(id);
275
+ const children = childMap.get(id);
276
+ if (!children || children.length === 0) {
277
+ placed.push({
278
+ id,
279
+ x: ox,
280
+ y: oy,
281
+ w: s.w,
282
+ h: s.h,
283
+ label: node?.label ?? {},
284
+ shape: "box",
285
+ isContainer: false
286
+ });
287
+ return;
288
+ }
289
+ const sizes = children.map(size);
290
+ if (arrangementOf(id) === "grid") {
291
+ const cols = gridColsOf(id, children);
292
+ const colW = Math.max(...sizes.map((s2) => s2.w));
293
+ const rowHeights = gridRowHeights(sizes, cols);
294
+ const rowTops = [];
295
+ let rowTop = oy + titleHeightOf(id) + PAD;
296
+ for (let row = 0; row < rowHeights.length; row++) {
297
+ rowTops[row] = rowTop;
298
+ rowTop += rowHeights[row] + GRID_GAP;
299
+ }
300
+ for (let i = 0; i < children.length; i++) {
301
+ const r = Math.floor(i / cols);
302
+ const cc = i % cols;
303
+ const cx = ox + PAD + cc * (colW + GRID_GAP) + (colW - sizes[i].w) / 2;
304
+ const cy = rowTops[r] + (rowHeights[r] - sizes[i].h) / 2;
305
+ place(children[i], cx, cy);
306
+ }
307
+ alignGridRows(children, sizes, rowHeights, rowTops, cols, childMap, doc.edges, positions, placed);
308
+ } else {
309
+ const maxW = Math.max(...sizes.map((s2) => s2.w));
310
+ let yy = oy + titleHeightOf(id) + PAD;
311
+ for (let i = 0; i < children.length; i++) {
312
+ const cx = ox + PAD + (maxW - sizes[i].w) / 2;
313
+ place(children[i], cx, yy);
314
+ yy += sizes[i].h + GRID_GAP;
315
+ }
316
+ }
317
+ placed.push({
318
+ id,
319
+ x: ox,
320
+ y: oy,
321
+ w: s.w,
322
+ h: s.h,
323
+ label: node?.label ?? {},
324
+ shape: "box",
325
+ isContainer: true
326
+ });
327
+ }
328
+ let cur = MARGIN;
329
+ for (const r of roots) {
330
+ const previousIds = new Set(positions.keys());
331
+ place(r, cur, MARGIN);
332
+ const subtreeIds = collectSubtreeIds(r, childMap);
333
+ const dy = rootAlignmentDelta(r, subtreeIds, previousIds, doc.edges, positions);
334
+ if (Math.abs(dy) >= EPS) shiftSubtree(subtreeIds, dy, positions, placed);
335
+ const sz = size(r);
336
+ cur += sz.w + ROOT_GAP;
337
+ }
338
+ const edges = makeBlockEdges(doc.edges, positions, placed, parentMap);
339
+ placed.sort((a, b) => {
340
+ if (a.isContainer !== b.isContainer) return a.isContainer ? -1 : 1;
341
+ if (a.isContainer && b.isContainer) {
342
+ const depthDiff = depthOf(a.id, parentMap) - depthOf(b.id, parentMap);
343
+ if (depthDiff !== 0) return depthDiff;
344
+ return b.w * b.h - a.w * a.h;
345
+ }
346
+ return a.y - b.y || a.x - b.x;
347
+ });
348
+ const boxes = [...positions.values()];
349
+ const edgePoints = edges.flatMap((edge) => edge.points);
350
+ const maxBoxX = boxes.length ? Math.max(...boxes.map((b) => b.x + b.w)) : MARGIN;
351
+ const maxBoxY = boxes.length ? Math.max(...boxes.map((b) => b.y + b.h)) : MARGIN;
352
+ const maxEdgeX = edgePoints.length ? Math.max(...edgePoints.map(([x]) => x)) : MARGIN;
353
+ const maxEdgeY = edgePoints.length ? Math.max(...edgePoints.map(([, y]) => y)) : MARGIN;
354
+ const width = Math.max(maxBoxX, maxEdgeX) + MARGIN;
355
+ const height = Math.max(maxBoxY, maxEdgeY) + MARGIN;
356
+ return { nodes: placed, edges, width, height, kind: "block" };
357
+ }
358
+ function layoutFlow(doc) {
359
+ const ids = [...doc.nodes.keys()];
360
+ const { byRank } = computeRanks(doc);
361
+ function shapeOf(id) {
362
+ const n = doc.nodes.get(id);
363
+ const ja = n?.label.ja ?? "";
364
+ const en = n?.label.en ?? "";
365
+ if (ja.endsWith("?") || en.endsWith("?")) return "diamond";
366
+ const inc = doc.edges.filter((e) => e.to === id).length;
367
+ const out = doc.edges.filter((e) => e.from === id).length;
368
+ if (inc === 0 || out === 0) return "round";
369
+ return "box";
370
+ }
371
+ const V_GAP = 14;
372
+ const H_GAP = 10;
373
+ const positions = /* @__PURE__ */ new Map();
374
+ const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
375
+ let y = MARGIN;
376
+ let maxX = 0;
377
+ for (const r of sortedRanks) {
378
+ const lane = byRank.get(r);
379
+ const widths = lane.map((id) => shapeOf(id) === "diamond" ? NODE_W * 1.2 : NODE_W);
380
+ const totalW = widths.reduce((a, b) => a + b, 0) + (lane.length - 1) * H_GAP;
381
+ let x = MARGIN;
382
+ const canvasW = Math.max(totalW + 2 * MARGIN, 200);
383
+ x = (canvasW - totalW) / 2;
384
+ for (let i = 0; i < lane.length; i++) {
385
+ positions.set(lane[i], { x, y, w: widths[i], h: NODE_H });
386
+ x += widths[i] + H_GAP;
387
+ }
388
+ if (x > maxX) maxX = x;
389
+ y += NODE_H + V_GAP;
390
+ }
391
+ for (const id of ids) {
392
+ if (!positions.has(id)) {
393
+ positions.set(id, { x: MARGIN, y, w: NODE_W, h: NODE_H });
394
+ y += NODE_H + V_GAP;
395
+ }
396
+ }
397
+ const placed = [];
398
+ for (const [id, b] of positions) {
399
+ placed.push({
400
+ id,
401
+ ...b,
402
+ label: doc.nodes.get(id)?.label ?? {},
403
+ shape: shapeOf(id),
404
+ isContainer: false
405
+ });
406
+ }
407
+ const edges = makeEdges(doc.edges, positions);
408
+ return { nodes: placed, edges, width: maxX + MARGIN, height: y + MARGIN, kind: "flow" };
409
+ }
410
+ function layoutState(doc) {
411
+ const { byRank } = computeRanks(doc);
412
+ function shapeOf(id) {
413
+ if (id === "*") return "circle";
414
+ return "round";
415
+ }
416
+ const V_GAP = 14, H_GAP = 10;
417
+ const positions = /* @__PURE__ */ new Map();
418
+ const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
419
+ let y = MARGIN;
420
+ let maxX = 0;
421
+ for (const r of sortedRanks) {
422
+ const lane = byRank.get(r);
423
+ const widths = lane.map((id) => shapeOf(id) === "circle" ? 6 : NODE_W);
424
+ const heights = lane.map((id) => shapeOf(id) === "circle" ? 6 : NODE_H);
425
+ const totalW = widths.reduce((a, b) => a + b, 0) + (lane.length - 1) * H_GAP;
426
+ const canvasW = Math.max(totalW + 2 * MARGIN, 200);
427
+ let x = (canvasW - totalW) / 2;
428
+ for (let i = 0; i < lane.length; i++) {
429
+ positions.set(lane[i], { x, y: y + (NODE_H - heights[i]) / 2, w: widths[i], h: heights[i] });
430
+ x += widths[i] + H_GAP;
431
+ }
432
+ if (x > maxX) maxX = x;
433
+ y += NODE_H + V_GAP;
434
+ }
435
+ for (const id of doc.nodes.keys()) {
436
+ if (!positions.has(id)) {
437
+ positions.set(id, { x: MARGIN, y, w: NODE_W, h: NODE_H });
438
+ y += NODE_H + V_GAP;
439
+ }
440
+ }
441
+ const placed = [];
442
+ for (const [id, b] of positions) {
443
+ placed.push({
444
+ id,
445
+ ...b,
446
+ label: doc.nodes.get(id)?.label ?? {},
447
+ shape: shapeOf(id),
448
+ isContainer: false
449
+ });
450
+ }
451
+ const edges = makeEdges(doc.edges, positions);
452
+ return { nodes: placed, edges, width: maxX + MARGIN, height: y + MARGIN, kind: "state" };
453
+ }
454
+ function layoutSeq(doc) {
455
+ const seen = /* @__PURE__ */ new Set();
456
+ const actors = [];
457
+ for (const e of doc.edges) {
458
+ for (const id of [e.from, e.to]) {
459
+ if (!seen.has(id)) {
460
+ seen.add(id);
461
+ actors.push(id);
462
+ }
463
+ }
464
+ }
465
+ for (const id of doc.nodes.keys()) {
466
+ if (!seen.has(id)) {
467
+ seen.add(id);
468
+ actors.push(id);
469
+ }
470
+ }
471
+ const ACTOR_W = 40, ACTOR_H = 12, COL_GAP = 28, MSG_GAP = 12;
472
+ const xOf = /* @__PURE__ */ new Map();
473
+ const placed = [];
474
+ let x = MARGIN;
475
+ for (const id of actors) {
476
+ xOf.set(id, x + ACTOR_W / 2);
477
+ placed.push({
478
+ id,
479
+ x,
480
+ y: MARGIN,
481
+ w: ACTOR_W,
482
+ h: ACTOR_H,
483
+ label: doc.nodes.get(id)?.label ?? {},
484
+ shape: "actor",
485
+ isContainer: false
486
+ });
487
+ x += ACTOR_W + COL_GAP;
488
+ }
489
+ let y = MARGIN + ACTOR_H + MSG_GAP;
490
+ const msgEdges = [];
491
+ for (const e of doc.edges) {
492
+ const xa = xOf.get(e.from);
493
+ const xb = xOf.get(e.to);
494
+ if (xa === void 0 || xb === void 0) continue;
495
+ msgEdges.push({
496
+ from: e.from,
497
+ to: e.to,
498
+ points: [[xa, y], [xb, y]],
499
+ label: e.label,
500
+ op: e.op
501
+ });
502
+ y += MSG_GAP;
503
+ }
504
+ const lifelines = actors.map((id) => ({
505
+ from: id,
506
+ to: id,
507
+ points: [[xOf.get(id), MARGIN + ACTOR_H], [xOf.get(id), y + MSG_GAP]],
508
+ op: "dashed",
509
+ isLifeline: true
510
+ }));
511
+ return {
512
+ nodes: placed,
513
+ edges: [...lifelines, ...msgEdges],
514
+ width: x,
515
+ height: y + MARGIN * 2,
516
+ kind: "seq"
517
+ };
518
+ }
519
+ function computeRanks(doc) {
520
+ const ids = [...doc.nodes.keys()];
521
+ const incoming = /* @__PURE__ */ new Map();
522
+ for (const id of ids) incoming.set(id, 0);
523
+ for (const e of doc.edges) incoming.set(e.to, (incoming.get(e.to) ?? 0) + 1);
524
+ const outgoing = /* @__PURE__ */ new Map();
525
+ for (const e of doc.edges) {
526
+ if (!outgoing.has(e.from)) outgoing.set(e.from, []);
527
+ outgoing.get(e.from).push(e.to);
528
+ }
529
+ let sources = ids.filter((id) => incoming.get(id) === 0);
530
+ if (sources.length === 0) {
531
+ sources = ids.includes("*") ? ["*"] : ids.length ? [ids[0]] : [];
532
+ }
533
+ const rank = /* @__PURE__ */ new Map();
534
+ const visited = /* @__PURE__ */ new Set();
535
+ for (const s of sources) {
536
+ rank.set(s, 0);
537
+ visited.add(s);
538
+ }
539
+ let frontier = [...sources];
540
+ while (frontier.length) {
541
+ const next = [];
542
+ for (const n of frontier) {
543
+ const r = rank.get(n);
544
+ for (const m of outgoing.get(n) ?? []) {
545
+ if (!visited.has(m)) {
546
+ rank.set(m, r + 1);
547
+ visited.add(m);
548
+ next.push(m);
549
+ }
550
+ }
551
+ }
552
+ frontier = next;
553
+ }
554
+ let maxRank = 0;
555
+ for (const r of rank.values()) if (r > maxRank) maxRank = r;
556
+ for (const id of ids) {
557
+ if (!visited.has(id)) {
558
+ maxRank++;
559
+ rank.set(id, maxRank);
560
+ visited.add(id);
561
+ }
562
+ }
563
+ const byRank = /* @__PURE__ */ new Map();
564
+ for (const [id, r] of rank) {
565
+ if (!byRank.has(r)) byRank.set(r, []);
566
+ byRank.get(r).push(id);
567
+ }
568
+ return { byRank };
569
+ }
570
+ function makeEdges(srcEdges, positions) {
571
+ return srcEdges.map((e) => {
572
+ const a = positions.get(e.from);
573
+ const b = positions.get(e.to);
574
+ if (!a || !b) {
575
+ return { from: e.from, to: e.to, points: [], label: e.label, op: e.op };
576
+ }
577
+ return {
578
+ from: e.from,
579
+ to: e.to,
580
+ points: orthogonalRoute(a, b),
581
+ label: e.label,
582
+ op: e.op
583
+ };
584
+ });
585
+ }
586
+ function hasLinearChildFlow(children, edges, childMap) {
587
+ const childSet = new Set(children);
588
+ const pairs = /* @__PURE__ */ new Set();
589
+ const incoming = /* @__PURE__ */ new Map();
590
+ const outgoing = /* @__PURE__ */ new Map();
591
+ for (const child of children) {
592
+ incoming.set(child, 0);
593
+ outgoing.set(child, 0);
594
+ }
595
+ for (const edge of edges) {
596
+ const from = topChildFor(edge.from, childSet, childMap);
597
+ const to = topChildFor(edge.to, childSet, childMap);
598
+ if (!from || !to || from === to) continue;
599
+ const key = `${from}|${to}`;
600
+ if (pairs.has(key)) continue;
601
+ pairs.add(key);
602
+ outgoing.set(from, (outgoing.get(from) ?? 0) + 1);
603
+ incoming.set(to, (incoming.get(to) ?? 0) + 1);
604
+ }
605
+ if (pairs.size < children.length - 1) return false;
606
+ let starts = 0;
607
+ let ends = 0;
608
+ for (const child of children) {
609
+ const inc = incoming.get(child) ?? 0;
610
+ const out = outgoing.get(child) ?? 0;
611
+ if (inc > 1 || out > 1) return false;
612
+ if (inc === 0 && out === 1) starts++;
613
+ if (inc === 1 && out === 0) ends++;
614
+ }
615
+ return starts === 1 && ends === 1;
616
+ }
617
+ function topChildFor(id, childSet, childMap) {
618
+ if (childSet.has(id)) return id;
619
+ for (const child of childSet) {
620
+ if (containsDescendant(child, id, childMap)) return child;
621
+ }
622
+ return void 0;
623
+ }
624
+ function containsDescendant(ancestor, id, childMap) {
625
+ const children = childMap.get(ancestor);
626
+ if (!children) return false;
627
+ for (const child of children) {
628
+ if (child === id || containsDescendant(child, id, childMap)) return true;
629
+ }
630
+ return false;
631
+ }
632
+ function collectSubtreeIds(id, childMap) {
633
+ const ids = /* @__PURE__ */ new Set([id]);
634
+ for (const child of childMap.get(id) ?? []) {
635
+ for (const descendant of collectSubtreeIds(child, childMap)) ids.add(descendant);
636
+ }
637
+ return ids;
638
+ }
639
+ function alignGridRows(children, sizes, rowHeights, rowTops, cols, childMap, edges, positions, placed) {
640
+ const rows = Math.ceil(children.length / cols);
641
+ for (let row = 0; row < rows; row++) {
642
+ const rowChildren = children.slice(row * cols, row * cols + cols);
643
+ const rowTop = rowTops[row];
644
+ const rowH = rowHeights[row];
645
+ if (rowTop === void 0 || rowH === void 0) continue;
646
+ for (const child of rowChildren) {
647
+ const childIndex = children.indexOf(child);
648
+ const childSize = sizes[childIndex];
649
+ if (!childSize || childSize.h >= rowH - EPS) continue;
650
+ const subtreeIds = collectSubtreeIds(child, childMap);
651
+ const siblingIds = /* @__PURE__ */ new Set();
652
+ for (const sibling of rowChildren) {
653
+ if (sibling === child) continue;
654
+ for (const id of collectSubtreeIds(sibling, childMap)) siblingIds.add(id);
655
+ }
656
+ const deltas = [];
657
+ for (const edge of edges) {
658
+ const fromChild = subtreeIds.has(edge.from);
659
+ const toChild = subtreeIds.has(edge.to);
660
+ if (fromChild && siblingIds.has(edge.to)) {
661
+ addWeightedDelta(
662
+ deltas,
663
+ centerY(positions.get(edge.to)) - centerY(positions.get(edge.from)),
664
+ alignmentWeight(edge, false)
665
+ );
666
+ } else if (toChild && siblingIds.has(edge.from)) {
667
+ addWeightedDelta(
668
+ deltas,
669
+ centerY(positions.get(edge.from)) - centerY(positions.get(edge.to)),
670
+ alignmentWeight(edge, true)
671
+ );
672
+ }
673
+ }
674
+ if (deltas.length === 0) continue;
675
+ deltas.sort((a, b) => a - b);
676
+ const desired = deltas[Math.floor(deltas.length / 2)];
677
+ const box = positions.get(child);
678
+ if (!box) continue;
679
+ const minDy = rowTop - box.y;
680
+ const maxDy = rowTop + rowH - childSize.h - box.y;
681
+ const dy = Math.min(maxDy, Math.max(minDy, desired));
682
+ if (Math.abs(dy) >= EPS) shiftSubtree(subtreeIds, dy, positions, placed);
683
+ }
684
+ }
685
+ }
686
+ function rootAlignmentDelta(rootId, subtreeIds, previousIds, edges, positions) {
687
+ const deltas = [];
688
+ for (const edge of edges) {
689
+ const fromCurrent = subtreeIds.has(edge.from);
690
+ const toCurrent = subtreeIds.has(edge.to);
691
+ const fromPrevious = previousIds.has(edge.from);
692
+ const toPrevious = previousIds.has(edge.to);
693
+ if (fromCurrent && toPrevious) {
694
+ addWeightedDelta(
695
+ deltas,
696
+ centerY(positions.get(edge.to)) - centerY(positions.get(edge.from)),
697
+ alignmentWeight(edge, false)
698
+ );
699
+ } else if (toCurrent && fromPrevious) {
700
+ addWeightedDelta(
701
+ deltas,
702
+ centerY(positions.get(edge.from)) - centerY(positions.get(edge.to)),
703
+ alignmentWeight(edge, true)
704
+ );
705
+ }
706
+ }
707
+ if (deltas.length === 0) return 0;
708
+ deltas.sort((a, b) => a - b);
709
+ const desired = deltas[Math.floor(deltas.length / 2)];
710
+ const root = positions.get(rootId);
711
+ if (!root) return desired;
712
+ return Math.max(MARGIN - root.y, desired);
713
+ }
714
+ function addWeightedDelta(deltas, delta, weight) {
715
+ if (!Number.isFinite(delta)) return;
716
+ for (let i = 0; i < weight; i++) deltas.push(delta);
717
+ }
718
+ function alignmentWeight(edge, currentIsTarget) {
719
+ const feedback = edge.op === "dashed" || edge.op === "dashed-arrow";
720
+ return (feedback ? 1 : 3) + (currentIsTarget ? 1 : 0);
721
+ }
722
+ function centerY(box) {
723
+ return box ? box.y + box.h / 2 : Number.NaN;
724
+ }
725
+ function shiftSubtree(ids, dy, positions, placed) {
726
+ for (const id of ids) {
727
+ const box = positions.get(id);
728
+ if (box) box.y += dy;
729
+ }
730
+ for (const node of placed) {
731
+ if (ids.has(node.id)) node.y += dy;
732
+ }
733
+ }
734
+ function makeBlockEdges(srcEdges, positions, obstacles, parentMap) {
735
+ const containerIds = new Set(obstacles.filter((o) => o.isContainer).map((o) => o.id));
736
+ const plans = srcEdges.map((edge, index) => {
737
+ const a = positions.get(edge.from);
738
+ const b = positions.get(edge.to);
739
+ if (!a || !b) {
740
+ return { edge, index, endpointBoundaries: [], endpointInteriorBarriers: [] };
741
+ }
742
+ const routeA = routeEndpointBox(edge.from, edge.to, positions, parentMap, containerIds) ?? a;
743
+ const routeB = routeEndpointBox(edge.to, edge.from, positions, parentMap, containerIds) ?? b;
744
+ const endpointBoundaries = [
745
+ ...routeA === a ? [] : [a],
746
+ ...routeB === b ? [] : [b]
747
+ ];
748
+ const endpointInteriorBarriers = [
749
+ ...isExternalContainerEndpoint(edge.from, edge.to, parentMap, containerIds) ? [a] : [],
750
+ ...isExternalContainerEndpoint(edge.to, edge.from, parentMap, containerIds) ? [b] : []
751
+ ];
752
+ return {
753
+ edge,
754
+ index,
755
+ routeA,
756
+ routeB,
757
+ endpointBoundaries,
758
+ endpointInteriorBarriers,
759
+ bounds: commonRoutingBounds(edge.from, edge.to, parentMap, positions)
760
+ };
761
+ });
762
+ return routePlansSequentially(plans, obstacles, parentMap);
763
+ }
764
+ function routePlansSequentially(plans, baseObstacles, parentMap) {
765
+ const routed = [];
766
+ for (const plan of plans) {
767
+ const searchBox = routeSearchBox(plan);
768
+ const routedArrowGuards = routed.flatMap((edge, index) => arrowGuardObstaclesFor(edge, index));
769
+ const routedEdgeGuards = routed.flatMap((edge, index) => edgeGuardObstaclesFor(edge, index));
770
+ const extraArrowGuards = routedArrowGuards.filter((guard) => guard.edgeIndex !== plan.index && !sharesRouteEndpoint(guard, plan.edge) && (!searchBox || boxesOverlap(searchBox, guard)));
771
+ const extraEdgeGuards = routedEdgeGuards.filter((guard) => shouldUseEdgeGuard(guard, plan, searchBox));
772
+ routed.push(routeBlockPlan(plan, [...baseObstacles, ...extraArrowGuards, ...extraEdgeGuards], parentMap));
773
+ }
774
+ return routed;
775
+ }
776
+ function routeBlockPlan(plan, obstacles, parentMap) {
777
+ const { edge, routeA, routeB } = plan;
778
+ if (!routeA || !routeB) {
779
+ return { from: edge.from, to: edge.to, points: [], label: edge.label, op: edge.op };
780
+ }
781
+ return {
782
+ from: edge.from,
783
+ to: edge.to,
784
+ points: avoidObstaclesRoute(
785
+ routeA,
786
+ routeB,
787
+ edge.from,
788
+ edge.to,
789
+ obstacles,
790
+ parentMap,
791
+ plan.bounds,
792
+ plan.endpointBoundaries,
793
+ plan.endpointInteriorBarriers,
794
+ edge.op
795
+ ),
796
+ label: edge.label,
797
+ op: edge.op
798
+ };
799
+ }
800
+ function arrowGuardObstaclesFor(edge, edgeIndex) {
801
+ if (edge.isLifeline || edge.points.length < 2) return [];
802
+ const guards = [];
803
+ if (hasEndArrow(edge.op)) {
804
+ guards.push(makeArrowGuard(edge, edgeIndex, edge.points[edge.points.length - 1], "end"));
805
+ }
806
+ if (edge.op === "bidir") {
807
+ guards.push(makeArrowGuard(edge, edgeIndex, edge.points[0], "start"));
808
+ }
809
+ return guards;
810
+ }
811
+ function hasEndArrow(op) {
812
+ return op !== "line" && op !== "dashed";
813
+ }
814
+ function makeArrowGuard(edge, edgeIndex, tip, side) {
815
+ const half = edge.op === "thick" ? 5 : 4.2;
816
+ return {
817
+ id: `__arrow_guard_${edgeIndex}_${side}`,
818
+ x: tip[0] - half,
819
+ y: tip[1] - half,
820
+ w: half * 2,
821
+ h: half * 2,
822
+ isContainer: false,
823
+ edgeIndex,
824
+ edgeFrom: edge.from,
825
+ edgeTo: edge.to
826
+ };
827
+ }
828
+ function sharesRouteEndpoint(guard, edge) {
829
+ return guard.edgeFrom === edge.from || guard.edgeFrom === edge.to || guard.edgeTo === edge.from || guard.edgeTo === edge.to;
830
+ }
831
+ function edgeGuardObstaclesFor(edge, edgeIndex) {
832
+ if (edge.points.length < 2) return [];
833
+ const guards = [];
834
+ const clear = 2.2;
835
+ const trim = 1.2;
836
+ for (let i = 0; i < edge.points.length - 1; i++) {
837
+ const a = edge.points[i];
838
+ const b = edge.points[i + 1];
839
+ const len = segmentLength(a, b);
840
+ if (len <= trim * 2) continue;
841
+ if (Math.abs(a[0] - b[0]) < EPS) {
842
+ const y1 = Math.min(a[1], b[1]) + trim;
843
+ const y2 = Math.max(a[1], b[1]) - trim;
844
+ guards.push({
845
+ id: `__edge_guard_${edgeIndex}_${i}`,
846
+ x: a[0] - clear,
847
+ y: y1,
848
+ w: clear * 2,
849
+ h: y2 - y1,
850
+ isContainer: false,
851
+ edgeIndex,
852
+ edgeOp: edge.op,
853
+ orientation: "vertical"
854
+ });
855
+ } else if (Math.abs(a[1] - b[1]) < EPS) {
856
+ const x1 = Math.min(a[0], b[0]) + trim;
857
+ const x2 = Math.max(a[0], b[0]) - trim;
858
+ guards.push({
859
+ id: `__edge_guard_${edgeIndex}_${i}`,
860
+ x: x1,
861
+ y: a[1] - clear,
862
+ w: x2 - x1,
863
+ h: clear * 2,
864
+ isContainer: false,
865
+ edgeIndex,
866
+ edgeOp: edge.op,
867
+ orientation: "horizontal"
868
+ });
869
+ }
870
+ }
871
+ return guards;
872
+ }
873
+ function shouldUseEdgeGuard(guard, plan, searchBox) {
874
+ if (guard.edgeIndex === plan.index) return false;
875
+ return !searchBox || boxesOverlap(searchBox, guard);
876
+ }
877
+ function routeSearchBox(plan) {
878
+ if (!plan.routeA || !plan.routeB) return void 0;
879
+ const left = Math.min(plan.routeA.x, plan.routeB.x);
880
+ const top = Math.min(plan.routeA.y, plan.routeB.y);
881
+ const right = Math.max(plan.routeA.x + plan.routeA.w, plan.routeB.x + plan.routeB.w);
882
+ const bottom = Math.max(plan.routeA.y + plan.routeA.h, plan.routeB.y + plan.routeB.h);
883
+ const pad = ROUTE_GAP * 4;
884
+ const candidate = {
885
+ x: left - pad,
886
+ y: top - pad,
887
+ w: right - left + pad * 2,
888
+ h: bottom - top + pad * 2
889
+ };
890
+ if (!plan.bounds) return candidate;
891
+ return {
892
+ x: Math.max(candidate.x, plan.bounds.x),
893
+ y: Math.max(candidate.y, plan.bounds.y),
894
+ w: Math.min(candidate.x + candidate.w, plan.bounds.x + plan.bounds.w) - Math.max(candidate.x, plan.bounds.x),
895
+ h: Math.min(candidate.y + candidate.h, plan.bounds.y + plan.bounds.h) - Math.max(candidate.y, plan.bounds.y)
896
+ };
897
+ }
898
+ function boxesOverlap(a, b) {
899
+ 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;
900
+ }
901
+ function routeEndpointBox(id, otherId, positions, parentMap, containerIds) {
902
+ const container = positions.get(id);
903
+ const other = positions.get(otherId);
904
+ if (!container || !other) return void 0;
905
+ if (!isAncestorOf(id, otherId, parentMap)) {
906
+ if (!containerIds.has(id)) return void 0;
907
+ return container;
908
+ }
909
+ const left = container.x + PAD;
910
+ const right = container.x + container.w - PAD;
911
+ const top = container.y + TITLE_H + PAD / 2;
912
+ const x = Math.min(right, Math.max(left, other.x + other.w / 2));
913
+ return { x: x - 0.1, y: top - 0.1, w: 0.2, h: 0.2 };
914
+ }
915
+ function isExternalContainerEndpoint(id, otherId, parentMap, containerIds) {
916
+ return containerIds.has(id) && !isAncestorOf(id, otherId, parentMap);
917
+ }
918
+ function isAncestorOf(ancestor, id, parentMap) {
919
+ let cur = id;
920
+ const seen = /* @__PURE__ */ new Set();
921
+ while (parentMap.has(cur) && !seen.has(cur)) {
922
+ seen.add(cur);
923
+ cur = parentMap.get(cur);
924
+ if (cur === ancestor) return true;
925
+ }
926
+ return false;
927
+ }
928
+ function depthOf(id, parentMap) {
929
+ let depth = 0;
930
+ let cur = id;
931
+ const seen = /* @__PURE__ */ new Set();
932
+ while (parentMap.has(cur) && !seen.has(cur)) {
933
+ seen.add(cur);
934
+ cur = parentMap.get(cur);
935
+ depth++;
936
+ }
937
+ return depth;
938
+ }
939
+ function avoidObstaclesRoute(a, b, from, to, obstacles, parentMap, bounds, endpointBoundaries = [], endpointInteriorBarriers = [], op = "line", strictEdgeGuards = true) {
940
+ const routedObstacles = obstacles.filter((o) => o.id !== from && o.id !== to);
941
+ const leafObstacles = routedObstacles.filter((o) => !o.isContainer);
942
+ const hardLeafObstacles = leafObstacles.filter((o) => o.id.startsWith("__arrow_guard_"));
943
+ const allowedContainers = allowedContainerIds(from, to, parentMap);
944
+ const passableContainerObstacles = routedObstacles.filter((o) => o.isContainer && allowedContainers.has(o.id));
945
+ const blockedContainerObstacles = routedObstacles.filter((o) => o.isContainer && !allowedContainers.has(o.id));
946
+ const externalContainerObstacles = passableContainerObstacles.filter((o) => isAncestorOf(o.id, from, parentMap) !== isAncestorOf(o.id, to, parentMap));
947
+ const boundaryObstacles = endpointBoundaries.map((box, index) => ({
948
+ ...box,
949
+ id: `__endpoint_boundary_${index}`,
950
+ isContainer: true
951
+ }));
952
+ const realLeafObstacles = leafObstacles.filter((o) => !o.id.startsWith("__"));
953
+ const edgeGuardObstacles = leafObstacles.filter(isEdgeGuardObstacle);
954
+ const straight = preferredStraightRoute(a, b);
955
+ 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, [
956
+ ...endpointBoundaries,
957
+ ...passableContainerObstacles,
958
+ ...blockedContainerObstacles
959
+ ], BORDER_CLEARANCE)) {
960
+ return straight;
961
+ }
962
+ const lanes = routeLanes(a, b, [...routedObstacles, ...boundaryObstacles], bounds);
963
+ let best = orthogonalRoute(a, b);
964
+ let bestScore = Number.POSITIVE_INFINITY;
965
+ for (const start of portsOf(a)) {
966
+ for (const end of portsOf(b)) {
967
+ const sourcePort = start.point;
968
+ const targetPort = end.point;
969
+ const sp = portLeadPoint(start);
970
+ const ep = portLeadPoint(end);
971
+ const candidates = [
972
+ [sourcePort, sp, [ep[0], sp[1]], ep, targetPort],
973
+ [sourcePort, sp, [sp[0], ep[1]], ep, targetPort]
974
+ ];
975
+ if (Math.abs(sp[0] - ep[0]) < EPS || Math.abs(sp[1] - ep[1]) < EPS) {
976
+ candidates.push([sourcePort, sp, ep, targetPort]);
977
+ }
978
+ for (const x of lanes.xs) {
979
+ candidates.push([sourcePort, sp, [x, sp[1]], [x, ep[1]], ep, targetPort]);
980
+ }
981
+ for (const y of lanes.ys) {
982
+ candidates.push([sourcePort, sp, [sp[0], y], [ep[0], y], ep, targetPort]);
983
+ }
984
+ for (const x of lanes.xs) {
985
+ for (const y of lanes.ys) {
986
+ candidates.push([sourcePort, sp, [x, sp[1]], [x, y], [ep[0], y], ep, targetPort]);
987
+ candidates.push([sourcePort, sp, [sp[0], y], [x, y], [x, ep[1]], ep, targetPort]);
988
+ }
989
+ }
990
+ for (const candidate of candidates) {
991
+ const normalized = normalizeRoute(candidate);
992
+ if (!isOrthogonalRoute(normalized)) continue;
993
+ if (!hasArrowTerminalClearance(normalized, op)) continue;
994
+ if (bounds && !routeFitsBounds(normalized, bounds)) continue;
995
+ if (routeOverlapsAnyBorder(normalized, [a, b], 0)) continue;
996
+ if (routeIntersectsAnyInterior(normalized, endpointInteriorBarriers, 0)) continue;
997
+ if (routeIntersectsAnyInterior(normalized, realLeafObstacles, OBSTACLE_PAD)) continue;
998
+ if (routeIntersectsAnyInterior(normalized, hardLeafObstacles, OBSTACLE_PAD)) continue;
999
+ if (strictEdgeGuards && routeSharesAnyEdgeGuardLane(normalized, edgeGuardObstacles)) continue;
1000
+ if (routeIntersectsAnyInterior(normalized, blockedContainerObstacles, OBSTACLE_PAD)) continue;
1001
+ if (routeOverlapsAnyBorder(normalized, [
1002
+ ...endpointBoundaries,
1003
+ ...passableContainerObstacles,
1004
+ ...blockedContainerObstacles
1005
+ ], BORDER_CLEARANCE)) continue;
1006
+ const score = scoreRoute(
1007
+ normalized,
1008
+ a,
1009
+ b,
1010
+ leafObstacles,
1011
+ blockedContainerObstacles,
1012
+ passableContainerObstacles,
1013
+ externalContainerObstacles,
1014
+ endpointBoundaries
1015
+ ) + portPairPenalty(a, b, start.side, end.side, op) + start.offsetPenalty + end.offsetPenalty;
1016
+ if (score < bestScore) {
1017
+ best = normalized;
1018
+ bestScore = score;
1019
+ }
1020
+ }
1021
+ }
1022
+ }
1023
+ if (bestScore === Number.POSITIVE_INFINITY) {
1024
+ if (strictEdgeGuards && edgeGuardObstacles.length > 0) {
1025
+ return avoidObstaclesRoute(
1026
+ a,
1027
+ b,
1028
+ from,
1029
+ to,
1030
+ obstacles,
1031
+ parentMap,
1032
+ bounds,
1033
+ endpointBoundaries,
1034
+ endpointInteriorBarriers,
1035
+ op,
1036
+ false
1037
+ );
1038
+ }
1039
+ return orthogonalRoute(a, b);
1040
+ }
1041
+ return best;
1042
+ }
1043
+ function preferredStraightRoute(a, b) {
1044
+ const acx = a.x + a.w / 2;
1045
+ const acy = a.y + a.h / 2;
1046
+ const bcx = b.x + b.w / 2;
1047
+ const bcy = b.y + b.h / 2;
1048
+ if (Math.abs(acy - bcy) < EPS) {
1049
+ if (a.x + a.w <= b.x) return [[a.x + a.w, acy], [b.x, bcy]];
1050
+ if (b.x + b.w <= a.x) return [[a.x, acy], [b.x + b.w, bcy]];
1051
+ }
1052
+ if (Math.abs(acx - bcx) < EPS) {
1053
+ if (a.y + a.h <= b.y) return [[acx, a.y + a.h], [bcx, b.y]];
1054
+ if (b.y + b.h <= a.y) return [[acx, a.y], [bcx, b.y + b.h]];
1055
+ }
1056
+ return void 0;
1057
+ }
1058
+ function hasArrowTerminalClearance(points, op) {
1059
+ if (points.length < 2) return false;
1060
+ if (op !== "line" && op !== "dashed") {
1061
+ if (terminalSegmentLength(points, false) < arrowTerminalClearance(op)) return false;
1062
+ }
1063
+ if (op === "bidir") {
1064
+ if (terminalSegmentLength(points, true) < arrowTerminalClearance(op)) return false;
1065
+ }
1066
+ return true;
1067
+ }
1068
+ function terminalSegmentLength(points, atStart) {
1069
+ if (points.length < 2) return 0;
1070
+ const a = atStart ? points[0] : points[points.length - 1];
1071
+ const b = atStart ? points[1] : points[points.length - 2];
1072
+ return segmentLength(a, b);
1073
+ }
1074
+ function arrowTerminalClearance(op) {
1075
+ return op === "thick" ? THICK_ARROW_TERMINAL_CLEARANCE : ARROW_TERMINAL_CLEARANCE;
1076
+ }
1077
+ function routeIntersectsAnyInterior(points, boxes, pad) {
1078
+ for (let i = 0; i < points.length - 1; i++) {
1079
+ for (const box of boxes) {
1080
+ if (boxInteriorOverlap(points[i], points[i + 1], box, pad) > EPS) return true;
1081
+ }
1082
+ }
1083
+ return false;
1084
+ }
1085
+ function routeOverlapsAnyBorder(points, boxes, tolerance) {
1086
+ for (let i = 0; i < points.length - 1; i++) {
1087
+ for (const box of boxes) {
1088
+ if (boxBorderProximityOverlap(points[i], points[i + 1], box, tolerance) > EPS) return true;
1089
+ }
1090
+ }
1091
+ return false;
1092
+ }
1093
+ function routeSharesAnyEdgeGuardLane(points, guards) {
1094
+ for (let i = 0; i < points.length - 1; i++) {
1095
+ for (const guard of guards) {
1096
+ if (segmentSharesEdgeGuardLane(points[i], points[i + 1], guard)) return true;
1097
+ }
1098
+ }
1099
+ return false;
1100
+ }
1101
+ function segmentSharesEdgeGuardLane(a, b, guard) {
1102
+ const segmentVertical = Math.abs(a[0] - b[0]) < EPS;
1103
+ const segmentHorizontal = Math.abs(a[1] - b[1]) < EPS;
1104
+ if (segmentVertical && guard.orientation === "vertical") {
1105
+ const guardX = guard.x + guard.w / 2;
1106
+ if (Math.abs(a[0] - guardX) > guard.w / 2 + EPS) return false;
1107
+ return intervalOverlap(a[1], b[1], guard.y, guard.y + guard.h) > EPS;
1108
+ }
1109
+ if (segmentHorizontal && guard.orientation === "horizontal") {
1110
+ const guardY = guard.y + guard.h / 2;
1111
+ if (Math.abs(a[1] - guardY) > guard.h / 2 + EPS) return false;
1112
+ return intervalOverlap(a[0], b[0], guard.x, guard.x + guard.w) > EPS;
1113
+ }
1114
+ return false;
1115
+ }
1116
+ function allowedContainerIds(from, to, parentMap) {
1117
+ return /* @__PURE__ */ new Set([from, to, ...ancestorsOf(from, parentMap), ...ancestorsOf(to, parentMap)]);
1118
+ }
1119
+ function portsOf(box) {
1120
+ const cx = box.x + box.w / 2;
1121
+ const cy = box.y + box.h / 2;
1122
+ const xOffset = Math.min(box.w * 0.25, Math.max(3, box.w / 2 - 4));
1123
+ const yOffset = Math.min(box.h * 0.25, Math.max(2, box.h / 2 - 3));
1124
+ const sidePenalty = 9;
1125
+ return [
1126
+ { point: [box.x + box.w, cy], side: "right", offsetPenalty: 0 },
1127
+ { point: [box.x, cy], side: "left", offsetPenalty: 0 },
1128
+ { point: [cx, box.y + box.h], side: "bottom", offsetPenalty: 0 },
1129
+ { point: [cx, box.y], side: "top", offsetPenalty: 0 },
1130
+ { point: [box.x + box.w, cy - yOffset], side: "right", offsetPenalty: sidePenalty },
1131
+ { point: [box.x + box.w, cy + yOffset], side: "right", offsetPenalty: sidePenalty },
1132
+ { point: [box.x, cy - yOffset], side: "left", offsetPenalty: sidePenalty },
1133
+ { point: [box.x, cy + yOffset], side: "left", offsetPenalty: sidePenalty },
1134
+ { point: [cx - xOffset, box.y + box.h], side: "bottom", offsetPenalty: sidePenalty },
1135
+ { point: [cx + xOffset, box.y + box.h], side: "bottom", offsetPenalty: sidePenalty },
1136
+ { point: [cx - xOffset, box.y], side: "top", offsetPenalty: sidePenalty },
1137
+ { point: [cx + xOffset, box.y], side: "top", offsetPenalty: sidePenalty }
1138
+ ];
1139
+ }
1140
+ function portLeadPoint(port) {
1141
+ const [x, y] = port.point;
1142
+ switch (port.side) {
1143
+ case "right":
1144
+ return [x + PORT_STUB, y];
1145
+ case "left":
1146
+ return [x - PORT_STUB, y];
1147
+ case "bottom":
1148
+ return [x, y + PORT_STUB];
1149
+ case "top":
1150
+ return [x, y - PORT_STUB];
1151
+ }
1152
+ }
1153
+ function portPairPenalty(source, target, start, end, op) {
1154
+ const sx = source.x + source.w / 2;
1155
+ const sy = source.y + source.h / 2;
1156
+ const tx = target.x + target.w / 2;
1157
+ const ty = target.y + target.h / 2;
1158
+ const dx = tx - sx;
1159
+ const dy = ty - sy;
1160
+ if (op === "dashed") return dashedPortPairPenalty(start, end, dx, dy, source, target);
1161
+ return sourcePortPenalty(start, dx, dy, source) + targetPortPenalty(end, dx, dy, target);
1162
+ }
1163
+ function dashedPortPairPenalty(start, end, dx, dy, source, target) {
1164
+ if (Math.abs(dy) > Math.min(source.h, target.h) * 0.8) {
1165
+ const startDesired = dy > 0 ? "bottom" : "top";
1166
+ const endDesired = dy > 0 ? "top" : "bottom";
1167
+ return dashedSidePenalty(start, startDesired) + dashedSidePenalty(end, endDesired);
1168
+ }
1169
+ if (Math.abs(dx) > Math.min(source.w, target.w) * 0.8) {
1170
+ const startDesired = dx > 0 ? "right" : "left";
1171
+ const endDesired = dx > 0 ? "left" : "right";
1172
+ return dashedSidePenalty(start, startDesired) + dashedSidePenalty(end, endDesired);
1173
+ }
1174
+ return 0;
1175
+ }
1176
+ function dashedSidePenalty(side, desired) {
1177
+ if (side === desired) return 0;
1178
+ if (side === oppositeSide(desired)) return 1400;
1179
+ return 450;
1180
+ }
1181
+ function sourcePortPenalty(side, dx, dy, box) {
1182
+ if (Math.abs(dy) > box.h * 0.8 && Math.abs(dy) >= Math.abs(dx) * VERTICAL_PORT_RATIO) {
1183
+ const desired = dy > 0 ? "bottom" : "top";
1184
+ if (side === desired) return 0;
1185
+ return side === oppositeSide(desired) ? 800 : 180;
1186
+ }
1187
+ if (Math.abs(dx) > box.w * 0.5) {
1188
+ const desired = dx > 0 ? "right" : "left";
1189
+ if (side === desired) return 0;
1190
+ return side === oppositeSide(desired) ? 900 : 90;
1191
+ }
1192
+ if (Math.abs(dy) > box.h * 0.8) {
1193
+ const desired = dy > 0 ? "bottom" : "top";
1194
+ if (side === desired) return 0;
1195
+ return side === oppositeSide(desired) ? 600 : 120;
1196
+ }
1197
+ return 0;
1198
+ }
1199
+ function targetPortPenalty(side, dx, dy, box) {
1200
+ if (Math.abs(dy) > box.h * 0.8 && Math.abs(dy) >= Math.abs(dx) * VERTICAL_PORT_RATIO) {
1201
+ const desired = dy > 0 ? "top" : "bottom";
1202
+ if (side === desired) return 0;
1203
+ return side === oppositeSide(desired) ? 1400 : 360;
1204
+ }
1205
+ if (Math.abs(dx) > box.w * 0.5 && Math.abs(dx) >= Math.abs(dy) * 1.2) {
1206
+ const desired = dx > 0 ? "left" : "right";
1207
+ if (side === desired) return 0;
1208
+ return side === oppositeSide(desired) ? 1200 : 220;
1209
+ }
1210
+ if (Math.abs(dy) > box.h * 0.35) {
1211
+ const desired = dy > 0 ? "top" : "bottom";
1212
+ if (side === desired) return 0;
1213
+ return side === "left" || side === "right" ? 9e3 : 1200;
1214
+ }
1215
+ if (Math.abs(dx) > box.w * 0.5) {
1216
+ const desired = dx > 0 ? "left" : "right";
1217
+ if (side === desired) return 0;
1218
+ return side === oppositeSide(desired) ? 1e3 : 160;
1219
+ }
1220
+ return 0;
1221
+ }
1222
+ function oppositeSide(side) {
1223
+ switch (side) {
1224
+ case "right":
1225
+ return "left";
1226
+ case "left":
1227
+ return "right";
1228
+ case "bottom":
1229
+ return "top";
1230
+ case "top":
1231
+ return "bottom";
1232
+ }
1233
+ }
1234
+ function routeLanes(a, b, obstacles, bounds) {
1235
+ const boxes = [a, b, ...obstacles];
1236
+ const laneMargin = ROUTE_GAP * 2;
1237
+ const minX = bounds ? bounds.x : Math.max(MARGIN, Math.min(...boxes.map((o) => o.x)) - laneMargin);
1238
+ const maxX = bounds ? bounds.x + bounds.w : Math.max(...boxes.map((o) => o.x + o.w)) + laneMargin;
1239
+ const minY = bounds ? bounds.y : Math.max(MARGIN, Math.min(...boxes.map((o) => o.y)) - laneMargin);
1240
+ const maxY = bounds ? bounds.y + bounds.h : Math.max(...boxes.map((o) => o.y + o.h)) + laneMargin;
1241
+ const xs = [(a.x + a.w / 2 + b.x + b.w / 2) / 2];
1242
+ const ys = [(a.y + a.h / 2 + b.y + b.h / 2) / 2];
1243
+ for (const box of boxes) {
1244
+ for (const gap of [ROUTE_GAP, ROUTE_GAP * 1.5, ROUTE_GAP * 2]) {
1245
+ xs.push(box.x - gap, box.x + box.w + gap);
1246
+ ys.push(box.y - gap, box.y + box.h + gap);
1247
+ }
1248
+ }
1249
+ if (bounds) {
1250
+ xs.push(bounds.x, bounds.x + bounds.w);
1251
+ ys.push(bounds.y, bounds.y + bounds.h);
1252
+ }
1253
+ return {
1254
+ xs: limitRouteLanes(
1255
+ uniqueSorted(xs.filter((x) => x >= minX && x <= maxX)),
1256
+ (a.x + a.w / 2 + b.x + b.w / 2) / 2
1257
+ ),
1258
+ ys: limitRouteLanes(
1259
+ uniqueSorted(ys.filter((y) => y >= minY && y <= maxY)),
1260
+ (a.y + a.h / 2 + b.y + b.h / 2) / 2
1261
+ )
1262
+ };
1263
+ }
1264
+ function limitRouteLanes(values, anchor) {
1265
+ if (values.length <= MAX_ROUTE_LANES) return values;
1266
+ const keep = /* @__PURE__ */ new Set([values[0], values[values.length - 1]]);
1267
+ for (const value of [...values].sort((a, b) => Math.abs(a - anchor) - Math.abs(b - anchor))) {
1268
+ keep.add(value);
1269
+ if (keep.size >= MAX_ROUTE_LANES) break;
1270
+ }
1271
+ return [...keep].sort((a, b) => a - b);
1272
+ }
1273
+ function commonRoutingBounds(from, to, parentMap, positions) {
1274
+ const common = nearestCommonAncestor(from, to, parentMap);
1275
+ if (!common) return void 0;
1276
+ const box = positions.get(common);
1277
+ if (!box) return void 0;
1278
+ const inset = ROUTE_GAP / 2;
1279
+ const top = box.y + TITLE_H + PAD / 2;
1280
+ const bottom = box.y + box.h - inset;
1281
+ return {
1282
+ x: box.x + inset,
1283
+ y: top,
1284
+ w: Math.max(0, box.w - inset * 2),
1285
+ h: Math.max(0, bottom - top)
1286
+ };
1287
+ }
1288
+ function nearestCommonAncestor(from, to, parentMap) {
1289
+ const toAncestors = new Set(ancestorsOf(to, parentMap));
1290
+ return ancestorsOf(from, parentMap).find((id) => toAncestors.has(id));
1291
+ }
1292
+ function ancestorsOf(id, parentMap) {
1293
+ const out = [];
1294
+ let cur = id;
1295
+ const seen = /* @__PURE__ */ new Set();
1296
+ while (parentMap.has(cur) && !seen.has(cur)) {
1297
+ seen.add(cur);
1298
+ cur = parentMap.get(cur);
1299
+ out.push(cur);
1300
+ }
1301
+ return out;
1302
+ }
1303
+ function routeFitsBounds(points, bounds) {
1304
+ const right = bounds.x + bounds.w;
1305
+ const bottom = bounds.y + bounds.h;
1306
+ return points.every(([x, y]) => x >= bounds.x - EPS && x <= right + EPS && y >= bounds.y - EPS && y <= bottom + EPS);
1307
+ }
1308
+ function uniqueSorted(values) {
1309
+ const seen = /* @__PURE__ */ new Set();
1310
+ const out = [];
1311
+ for (const value of values) {
1312
+ const rounded = Math.round(value * 1e3) / 1e3;
1313
+ const key = rounded.toFixed(3);
1314
+ if (seen.has(key)) continue;
1315
+ seen.add(key);
1316
+ out.push(rounded);
1317
+ }
1318
+ return out.sort((a, b) => a - b);
1319
+ }
1320
+ function normalizeRoute(points) {
1321
+ const deduped = [];
1322
+ for (const point of points) {
1323
+ const prev = deduped[deduped.length - 1];
1324
+ if (!prev || Math.abs(prev[0] - point[0]) >= EPS || Math.abs(prev[1] - point[1]) >= EPS) {
1325
+ deduped.push(point);
1326
+ }
1327
+ }
1328
+ const out = [];
1329
+ for (const point of deduped) {
1330
+ out.push(point);
1331
+ while (out.length >= 3) {
1332
+ const a = out[out.length - 3];
1333
+ const b = out[out.length - 2];
1334
+ const c = out[out.length - 1];
1335
+ const sameX = Math.abs(a[0] - b[0]) < EPS && Math.abs(b[0] - c[0]) < EPS;
1336
+ const sameY = Math.abs(a[1] - b[1]) < EPS && Math.abs(b[1] - c[1]) < EPS;
1337
+ if (!sameX && !sameY) break;
1338
+ out.splice(out.length - 2, 1);
1339
+ }
1340
+ }
1341
+ return out;
1342
+ }
1343
+ function isOrthogonalRoute(points) {
1344
+ if (points.length < 2) return false;
1345
+ for (let i = 0; i < points.length - 1; i++) {
1346
+ const a = points[i];
1347
+ const b = points[i + 1];
1348
+ if (Math.abs(a[0] - b[0]) >= EPS && Math.abs(a[1] - b[1]) >= EPS) return false;
1349
+ }
1350
+ return true;
1351
+ }
1352
+ function scoreRoute(points, source, target, leafObstacles, blockedContainerObstacles, passableContainerObstacles, externalContainerObstacles, endpointBoundaries) {
1353
+ let score = 0;
1354
+ for (let i = 0; i < points.length - 1; i++) {
1355
+ const a = points[i];
1356
+ const b = points[i + 1];
1357
+ score += segmentLength(a, b);
1358
+ const sourceInterior = boxInteriorOverlap(a, b, source, 0);
1359
+ if (sourceInterior > 0) score += 22e4 + sourceInterior * 1500;
1360
+ const targetInterior = boxInteriorOverlap(a, b, target, 0);
1361
+ if (targetInterior > 0) score += 22e4 + targetInterior * 1500;
1362
+ const sourceBorder = boxBorderOverlap(a, b, source);
1363
+ if (sourceBorder > 0) score += 8e3 + sourceBorder * 120;
1364
+ const targetBorder = boxBorderOverlap(a, b, target);
1365
+ if (targetBorder > 0) score += 8e3 + targetBorder * 120;
1366
+ for (const boundary of endpointBoundaries) {
1367
+ const overlap = boxBorderOverlap(a, b, boundary);
1368
+ if (overlap > 0) score += 12e3 + overlap * 160;
1369
+ }
1370
+ for (const obstacle of leafObstacles) {
1371
+ const overlap = boxInteriorOverlap(a, b, obstacle, OBSTACLE_PAD);
1372
+ if (overlap > 0) score += leafObstaclePenalty(obstacle, a, b, overlap);
1373
+ }
1374
+ for (const obstacle of blockedContainerObstacles) {
1375
+ const overlap = boxInteriorOverlap(a, b, obstacle, OBSTACLE_PAD);
1376
+ if (overlap > 0) score += 18e4 + overlap * 1200;
1377
+ const border = boxBorderOverlap(a, b, obstacle);
1378
+ if (border > 0) score += 18e3 + border * 300;
1379
+ }
1380
+ for (const obstacle of passableContainerObstacles) {
1381
+ const titleOverlap = boxInteriorOverlap(a, b, containerTitleRoutingBand(obstacle), OBSTACLE_PAD);
1382
+ if (titleOverlap > 0) score += 14e3 + titleOverlap * 120;
1383
+ const overlap = boxBorderOverlap(a, b, obstacle);
1384
+ if (overlap > 0) score += 6e3 + overlap * 120;
1385
+ }
1386
+ for (const obstacle of externalContainerObstacles) {
1387
+ const overlap = boxInteriorOverlap(a, b, obstacle, 0);
1388
+ if (overlap > 0) score += overlap * 40;
1389
+ }
1390
+ }
1391
+ score += Math.max(0, points.length - 2) * 3;
1392
+ score += routeExcursionPenalty(points, source, target);
1393
+ return score;
1394
+ }
1395
+ function routeExcursionPenalty(points, source, target) {
1396
+ const pad = ROUTE_GAP * 5;
1397
+ const left = Math.min(source.x, target.x) - pad;
1398
+ const top = Math.min(source.y, target.y) - pad;
1399
+ const right = Math.max(source.x + source.w, target.x + target.w) + pad;
1400
+ const bottom = Math.max(source.y + source.h, target.y + target.h) + pad;
1401
+ let penalty = 0;
1402
+ for (const [x, y] of points) {
1403
+ if (x < left) penalty += (left - x) * 90;
1404
+ if (x > right) penalty += (x - right) * 90;
1405
+ if (y < top) penalty += (top - y) * 90;
1406
+ if (y > bottom) penalty += (y - bottom) * 90;
1407
+ }
1408
+ return penalty;
1409
+ }
1410
+ function leafObstaclePenalty(obstacle, a, b, overlap) {
1411
+ if (obstacle.id.startsWith("__arrow_guard_")) return 7e4 + overlap * 700;
1412
+ if (isEdgeGuardObstacle(obstacle)) return edgeGuardPenalty(obstacle, a, b, overlap);
1413
+ return 16e4 + overlap * 1200;
1414
+ }
1415
+ function edgeGuardPenalty(obstacle, a, b, overlap) {
1416
+ const segmentVertical = Math.abs(a[0] - b[0]) < EPS;
1417
+ const guardVertical = obstacle.orientation === "vertical";
1418
+ if (segmentVertical === guardVertical) return 85e3 + overlap * 1200;
1419
+ return 500 + overlap * 160;
1420
+ }
1421
+ function isEdgeGuardObstacle(obstacle) {
1422
+ return obstacle.id.startsWith("__edge_guard_") && "orientation" in obstacle && (obstacle.orientation === "vertical" || obstacle.orientation === "horizontal");
1423
+ }
1424
+ function containerTitleRoutingBand(container) {
1425
+ return {
1426
+ x: container.x,
1427
+ y: container.y,
1428
+ w: container.w,
1429
+ h: TITLE_H + PAD
1430
+ };
1431
+ }
1432
+ function segmentLength(a, b) {
1433
+ return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);
1434
+ }
1435
+ function boxInteriorOverlap(a, b, box, pad) {
1436
+ const left = box.x - pad;
1437
+ const right = box.x + box.w + pad;
1438
+ const top = box.y - pad;
1439
+ const bottom = box.y + box.h + pad;
1440
+ if (Math.abs(a[0] - b[0]) < EPS) {
1441
+ const x = a[0];
1442
+ if (x <= left || x >= right) return 0;
1443
+ return intervalOverlap(a[1], b[1], top, bottom);
1444
+ }
1445
+ if (Math.abs(a[1] - b[1]) < EPS) {
1446
+ const y = a[1];
1447
+ if (y <= top || y >= bottom) return 0;
1448
+ return intervalOverlap(a[0], b[0], left, right);
1449
+ }
1450
+ return 0;
1451
+ }
1452
+ function boxBorderOverlap(a, b, box) {
1453
+ return boxBorderProximityOverlap(a, b, box, 0);
1454
+ }
1455
+ function boxBorderProximityOverlap(a, b, box, tolerance) {
1456
+ if (Math.abs(a[0] - b[0]) < EPS) {
1457
+ const x = a[0];
1458
+ if (Math.abs(x - box.x) > tolerance + EPS && Math.abs(x - (box.x + box.w)) > tolerance + EPS) return 0;
1459
+ return intervalOverlap(a[1], b[1], box.y, box.y + box.h);
1460
+ }
1461
+ if (Math.abs(a[1] - b[1]) < EPS) {
1462
+ const y = a[1];
1463
+ if (Math.abs(y - box.y) > tolerance + EPS && Math.abs(y - (box.y + box.h)) > tolerance + EPS) return 0;
1464
+ return intervalOverlap(a[0], b[0], box.x, box.x + box.w);
1465
+ }
1466
+ return 0;
1467
+ }
1468
+ function intervalOverlap(a1, a2, b1, b2) {
1469
+ const minA = Math.min(a1, a2);
1470
+ const maxA = Math.max(a1, a2);
1471
+ const minB = Math.min(b1, b2);
1472
+ const maxB = Math.max(b1, b2);
1473
+ return Math.max(0, Math.min(maxA, maxB) - Math.max(minA, minB));
1474
+ }
1475
+ function orthogonalRoute(a, b) {
1476
+ const ca = { x: a.x + a.w / 2, y: a.y + a.h / 2 };
1477
+ const cb = { x: b.x + b.w / 2, y: b.y + b.h / 2 };
1478
+ const dx = cb.x - ca.x;
1479
+ const dy = cb.y - ca.y;
1480
+ let pa, pb;
1481
+ const separatedHorizontally = a.x + a.w <= b.x || b.x + b.w <= a.x;
1482
+ const separatedVertically = a.y + a.h <= b.y || b.y + b.h <= a.y;
1483
+ const horizontal = separatedHorizontally ? true : separatedVertically ? false : Math.abs(dx) > Math.abs(dy);
1484
+ if (horizontal) {
1485
+ pa = [dx > 0 ? a.x + a.w : a.x, ca.y];
1486
+ pb = [dx > 0 ? b.x : b.x + b.w, cb.y];
1487
+ if (Math.abs(pa[1] - pb[1]) < 0.5) return [pa, pb];
1488
+ const midX = (pa[0] + pb[0]) / 2;
1489
+ return [pa, [midX, pa[1]], [midX, pb[1]], pb];
1490
+ } else {
1491
+ pa = [ca.x, dy > 0 ? a.y + a.h : a.y];
1492
+ pb = [cb.x, dy > 0 ? b.y : b.y + b.h];
1493
+ if (Math.abs(pa[0] - pb[0]) < 0.5) return [pa, pb];
1494
+ const midY = (pa[1] + pb[1]) / 2;
1495
+ return [pa, [pa[0], midY], [pb[0], midY], pb];
1496
+ }
1497
+ }
1498
+
1499
+ // src/core/render.ts
1500
+ var NS = "http://www.w3.org/2000/svg";
1501
+ var FONT_FAMILY = '"Hiragino Sans", "Yu Gothic", "Noto Sans CJK JP", sans-serif';
1502
+ var LABEL_OFFSET = 4;
1503
+ var LABEL_FONT_JA = 2.4;
1504
+ var LABEL_FONT_EN = 2.1;
1505
+ var LABEL_GAP = 0.6;
1506
+ var LABEL_OFFSET_STEPS = [0, 3.2, 6.4];
1507
+ var EPS2 = 1e-3;
1508
+ function render(laid, opts = {}) {
1509
+ const lang = opts.lang ?? "ja";
1510
+ const svg = el("svg", {
1511
+ xmlns: NS,
1512
+ viewBox: `0 0 ${laid.width} ${laid.height}`,
1513
+ "font-family": FONT_FAMILY,
1514
+ "shape-rendering": "geometricPrecision"
1515
+ });
1516
+ const containers = laid.nodes.filter((n) => n.isContainer);
1517
+ const leaves = laid.nodes.filter((n) => !n.isContainer);
1518
+ const labelBoxes = [];
1519
+ for (const c of containers) svg.appendChild(renderContainer(c, lang));
1520
+ for (const e of laid.edges) svg.appendChild(renderEdge(e, lang, laid, labelBoxes));
1521
+ for (const e of laid.edges) svg.appendChild(renderEdgeHeads(e));
1522
+ for (const n of leaves) svg.appendChild(renderNode(n, lang));
1523
+ return svg;
1524
+ }
1525
+ function renderContainer(n, lang) {
1526
+ const g = el("g");
1527
+ g.appendChild(el("rect", {
1528
+ x: n.x,
1529
+ y: n.y,
1530
+ width: n.w,
1531
+ height: n.h,
1532
+ fill: "white",
1533
+ stroke: "#000",
1534
+ "stroke-width": 0.3
1535
+ }));
1536
+ const labels = pickLabel(n.label, lang);
1537
+ if (labels.length || n.id) {
1538
+ const t = el("text", {
1539
+ x: n.x + 2,
1540
+ y: n.y + 3.5,
1541
+ "font-size": 2.6,
1542
+ fill: "#000"
1543
+ });
1544
+ t.textContent = (n.id ? n.id + " " : "") + (labels[0] ?? "");
1545
+ g.appendChild(t);
1546
+ }
1547
+ return g;
1548
+ }
1549
+ function renderNode(n, lang) {
1550
+ const g = el("g");
1551
+ g.appendChild(renderShape(n));
1552
+ const lines = [];
1553
+ if (n.id && n.id !== "*") lines.push(n.id);
1554
+ const labels = pickLabel(n.label, lang);
1555
+ for (const l of labels) lines.push(l);
1556
+ if (lines.length === 0) return g;
1557
+ const fontSize = 2.8;
1558
+ const lineH = fontSize * 1.2;
1559
+ const totalH = lines.length * lineH;
1560
+ const startY = n.y + n.h / 2 - totalH / 2 + lineH * 0.8;
1561
+ for (let i = 0; i < lines.length; i++) {
1562
+ const t = el("text", {
1563
+ x: n.x + n.w / 2,
1564
+ y: startY + i * lineH,
1565
+ "font-size": fontSize,
1566
+ fill: "#000",
1567
+ "text-anchor": "middle"
1568
+ });
1569
+ t.textContent = lines[i];
1570
+ g.appendChild(t);
1571
+ }
1572
+ return g;
1573
+ }
1574
+ function renderShape(n) {
1575
+ const stroke = "#000";
1576
+ const fill = "white";
1577
+ const sw = 0.4;
1578
+ switch (n.shape) {
1579
+ case "round":
1580
+ return el("rect", {
1581
+ x: n.x,
1582
+ y: n.y,
1583
+ width: n.w,
1584
+ height: n.h,
1585
+ rx: Math.min(n.w, n.h) / 2,
1586
+ ry: Math.min(n.w, n.h) / 2,
1587
+ fill,
1588
+ stroke,
1589
+ "stroke-width": sw
1590
+ });
1591
+ case "circle": {
1592
+ const r = Math.min(n.w, n.h) / 2;
1593
+ return el("circle", {
1594
+ cx: n.x + n.w / 2,
1595
+ cy: n.y + n.h / 2,
1596
+ r,
1597
+ fill: "#000",
1598
+ stroke
1599
+ });
1600
+ }
1601
+ case "diamond": {
1602
+ const cx = n.x + n.w / 2;
1603
+ const cy = n.y + n.h / 2;
1604
+ const pts = [[cx, n.y], [n.x + n.w, cy], [cx, n.y + n.h], [n.x, cy]].map((p) => p.join(",")).join(" ");
1605
+ return el("polygon", { points: pts, fill, stroke, "stroke-width": sw });
1606
+ }
1607
+ case "actor":
1608
+ case "box":
1609
+ default:
1610
+ return el("rect", {
1611
+ x: n.x,
1612
+ y: n.y,
1613
+ width: n.w,
1614
+ height: n.h,
1615
+ fill,
1616
+ stroke,
1617
+ "stroke-width": sw
1618
+ });
1619
+ }
1620
+ }
1621
+ function renderEdge(e, lang, laid, labelBoxes) {
1622
+ const g = el("g");
1623
+ if (e.points.length < 2) return g;
1624
+ const bodyPoints = edgeBodyPoints(e);
1625
+ if (bodyPoints.length < 2) return g;
1626
+ const d = bodyPoints.map((p, i) => i === 0 ? `M ${p[0]} ${p[1]}` : `L ${p[0]} ${p[1]}`).join(" ");
1627
+ const path = el("path", {
1628
+ d,
1629
+ fill: "none",
1630
+ stroke: "#000",
1631
+ "stroke-width": strokeWidth(e.op),
1632
+ "stroke-linecap": "butt",
1633
+ "stroke-linejoin": "miter"
1634
+ });
1635
+ const dash = strokeDash(e.op);
1636
+ if (dash) path.setAttribute("stroke-dasharray", dash);
1637
+ g.appendChild(path);
1638
+ const labels = pickLabel(e.label, lang);
1639
+ if (labels.length) {
1640
+ const ja = labels[0];
1641
+ const en = labels[1];
1642
+ const placement = chooseLabelPlacement(e, labels, laid, labelBoxes);
1643
+ labelBoxes.push(expandBox(placement.box, 0.8));
1644
+ const drawText = (text, x, y, fontSize, anchor, baseline) => {
1645
+ const t = el("text", {
1646
+ x,
1647
+ y,
1648
+ "font-size": fontSize,
1649
+ fill: "#000",
1650
+ "text-anchor": anchor,
1651
+ "dominant-baseline": baseline
1652
+ });
1653
+ t.textContent = text;
1654
+ g.appendChild(t);
1655
+ };
1656
+ drawText(ja, placement.ja.x, placement.ja.y, LABEL_FONT_JA, placement.anchor, placement.ja.baseline);
1657
+ if (en && placement.en) {
1658
+ drawText(en, placement.en.x, placement.en.y, LABEL_FONT_EN, placement.anchor, placement.en.baseline);
1659
+ }
1660
+ }
1661
+ return g;
1662
+ }
1663
+ function renderEdgeHeads(e) {
1664
+ const g = el("g");
1665
+ if (e.isLifeline) return g;
1666
+ const endHead = arrowHeadFor(e, false);
1667
+ const startHead = arrowHeadFor(e, true);
1668
+ if (endHead) g.appendChild(endHead);
1669
+ if (startHead) g.appendChild(startHead);
1670
+ return g;
1671
+ }
1672
+ function strokeWidth(op) {
1673
+ return op === "thick" ? 0.7 : 0.4;
1674
+ }
1675
+ function strokeDash(op) {
1676
+ return op === "dashed" || op === "dashed-arrow" ? "1.4 1.2" : null;
1677
+ }
1678
+ function edgeBodyPoints(e) {
1679
+ let points = e.points.map((p) => [p[0], p[1]]);
1680
+ if (e.op !== "line" && e.op !== "dashed") {
1681
+ points = trimPolyline(points, false, arrowLength(e.op));
1682
+ }
1683
+ if (e.op === "bidir") {
1684
+ points = trimPolyline(points, true, arrowLength(e.op));
1685
+ }
1686
+ return points;
1687
+ }
1688
+ function trimPolyline(points, atStart, amount) {
1689
+ if (points.length < 2 || amount <= 0) return points;
1690
+ const out = atStart ? [...points].reverse() : [...points];
1691
+ let remaining = amount;
1692
+ while (out.length >= 2 && remaining > 0) {
1693
+ const tip = out[out.length - 1];
1694
+ const prev = out[out.length - 2];
1695
+ const dx = prev[0] - tip[0];
1696
+ const dy = prev[1] - tip[1];
1697
+ const len = Math.hypot(dx, dy);
1698
+ if (len < EPS2) {
1699
+ out.pop();
1700
+ continue;
1701
+ }
1702
+ if (len > remaining) {
1703
+ out[out.length - 1] = [
1704
+ tip[0] + dx / len * remaining,
1705
+ tip[1] + dy / len * remaining
1706
+ ];
1707
+ remaining = 0;
1708
+ } else {
1709
+ out.pop();
1710
+ remaining -= len;
1711
+ }
1712
+ }
1713
+ return atStart ? out.reverse() : out;
1714
+ }
1715
+ function arrowLength(op) {
1716
+ return op === "thick" ? 3.4 : 2.8;
1717
+ }
1718
+ function arrowHeadFor(e, atStart) {
1719
+ if (e.points.length < 2) return null;
1720
+ if (atStart && e.op !== "bidir") return null;
1721
+ if (!atStart && (e.op === "line" || e.op === "dashed")) return null;
1722
+ const points = atStart ? e.points : [...e.points].reverse();
1723
+ return renderArrowHead(points[0], points[1], e.op === "thick");
1724
+ }
1725
+ function renderArrowHead(tip, next, bold) {
1726
+ const dx = next[0] - tip[0];
1727
+ const dy = next[1] - tip[1];
1728
+ const len = Math.hypot(dx, dy);
1729
+ if (len < 1e-3) return null;
1730
+ const ux = dx / len;
1731
+ const uy = dy / len;
1732
+ const arrowLen = bold ? 3.4 : 2.8;
1733
+ const halfW = bold ? 1.6 : 1.25;
1734
+ const bx = tip[0] + ux * arrowLen;
1735
+ const by = tip[1] + uy * arrowLen;
1736
+ const px = -uy;
1737
+ const py = ux;
1738
+ const pts = [
1739
+ tip,
1740
+ [bx + px * halfW, by + py * halfW],
1741
+ [bx - px * halfW, by - py * halfW]
1742
+ ].map((p) => p.join(",")).join(" ");
1743
+ return el("polygon", {
1744
+ points: pts,
1745
+ fill: "#000",
1746
+ stroke: "#000",
1747
+ "stroke-width": 0
1748
+ });
1749
+ }
1750
+ function el(tag, attrs = {}) {
1751
+ const node = document.createElementNS(NS, tag);
1752
+ for (const [k, v] of Object.entries(attrs)) node.setAttribute(k, String(v));
1753
+ return node;
1754
+ }
1755
+ function pickLabel(b, lang) {
1756
+ if (!b) return [];
1757
+ if (lang === "ja") return b.ja ? [b.ja] : b.en ? [b.en] : [];
1758
+ if (lang === "en") return b.en ? [b.en] : b.ja ? [b.ja] : [];
1759
+ const out = [];
1760
+ if (b.ja) out.push(b.ja);
1761
+ if (b.en) out.push(b.en);
1762
+ return out;
1763
+ }
1764
+ function chooseLabelPlacement(edge, labels, laid, occupiedLabels = []) {
1765
+ const candidates = [];
1766
+ const textW = Math.max(...labels.map((label, index) => estimateTextWidth(
1767
+ label,
1768
+ index === 0 ? LABEL_FONT_JA : LABEL_FONT_EN
1769
+ )));
1770
+ const textH = labels.length > 1 ? LABEL_FONT_JA + LABEL_FONT_EN + LABEL_GAP : LABEL_FONT_JA;
1771
+ for (let i = 0; i < edge.points.length - 1; i++) {
1772
+ const a = edge.points[i];
1773
+ const b = edge.points[i + 1];
1774
+ const dx = b[0] - a[0];
1775
+ const dy = b[1] - a[1];
1776
+ const len = Math.abs(dx) + Math.abs(dy);
1777
+ if (len < 8) continue;
1778
+ for (const t of [0.5, 0.35, 0.65, 0.22, 0.78]) {
1779
+ const x = a[0] + dx * t;
1780
+ const y = a[1] + dy * t;
1781
+ for (const offsetStep of LABEL_OFFSET_STEPS) {
1782
+ const offset = LABEL_OFFSET + offsetStep;
1783
+ const offsetPenalty = offsetStep * 9;
1784
+ if (Math.abs(dy) < EPS2) {
1785
+ candidates.push(makeHorizontalLabel(edge, laid, occupiedLabels, labels, textW, textH, a, b, x, y, "above", offset, offsetPenalty));
1786
+ candidates.push(makeHorizontalLabel(edge, laid, occupiedLabels, labels, textW, textH, a, b, x, y, "below", offset, offsetPenalty));
1787
+ } else if (Math.abs(dx) < EPS2) {
1788
+ candidates.push(makeVerticalLabel(edge, laid, occupiedLabels, labels, textW, textH, a, b, x, y, "right", offset, offsetPenalty));
1789
+ candidates.push(makeVerticalLabel(edge, laid, occupiedLabels, labels, textW, textH, a, b, x, y, "left", offset, offsetPenalty));
1790
+ }
1791
+ }
1792
+ }
1793
+ }
1794
+ if (candidates.length === 0) {
1795
+ const [a, b] = longestSegment(edge.points);
1796
+ const x = (a[0] + b[0]) / 2;
1797
+ const y = (a[1] + b[1]) / 2;
1798
+ 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);
1799
+ }
1800
+ candidates.sort((a, b) => a.score - b.score);
1801
+ return candidates[0];
1802
+ }
1803
+ function makeHorizontalLabel(edge, laid, occupiedLabels, labels, textW, textH, lineA, lineB, x, lineY, side, offset, offsetPenalty) {
1804
+ const top = side === "above" ? lineY - offset - textH : lineY + offset;
1805
+ const box = { x: x - textW / 2, y: top, w: textW, h: textH };
1806
+ let jaY;
1807
+ let enY;
1808
+ if (labels.length > 1) {
1809
+ jaY = top + LABEL_FONT_JA;
1810
+ enY = jaY + LABEL_GAP + LABEL_FONT_EN;
1811
+ } else {
1812
+ jaY = top + LABEL_FONT_JA;
1813
+ }
1814
+ const placement = {
1815
+ box,
1816
+ lineA,
1817
+ lineB,
1818
+ score: 0,
1819
+ vertical: false,
1820
+ anchor: "middle",
1821
+ ja: { x, y: jaY, baseline: "alphabetic" },
1822
+ en: enY === void 0 ? void 0 : { x, y: enY, baseline: "alphabetic" }
1823
+ };
1824
+ placement.score = labelPlacementScore(placement, edge, laid, occupiedLabels) + (side === "above" ? 0 : 8) + offsetPenalty;
1825
+ return placement;
1826
+ }
1827
+ function makeVerticalLabel(edge, laid, occupiedLabels, labels, textW, textH, lineA, lineB, lineX, y, side, offset, offsetPenalty) {
1828
+ const x = side === "right" ? lineX + offset : lineX - offset;
1829
+ const box = {
1830
+ x: side === "right" ? x : x - textW,
1831
+ y: y - textH / 2,
1832
+ w: textW,
1833
+ h: textH
1834
+ };
1835
+ const anchor = side === "right" ? "start" : "end";
1836
+ const jaY = labels.length > 1 ? y - LABEL_FONT_JA / 2 - LABEL_GAP / 2 : y;
1837
+ const enY = labels.length > 1 ? y + LABEL_FONT_EN / 2 + LABEL_GAP / 2 : void 0;
1838
+ const placement = {
1839
+ box,
1840
+ lineA,
1841
+ lineB,
1842
+ score: 0,
1843
+ vertical: true,
1844
+ anchor,
1845
+ ja: { x, y: jaY, baseline: "middle" },
1846
+ en: enY === void 0 ? void 0 : { x, y: enY, baseline: "middle" }
1847
+ };
1848
+ placement.score = labelPlacementScore(placement, edge, laid, occupiedLabels) + (side === "right" ? 0 : 8) + offsetPenalty;
1849
+ return placement;
1850
+ }
1851
+ function labelPlacementScore(placement, edge, laid, occupiedLabels) {
1852
+ let score = placement.vertical ? 5 : 0;
1853
+ const box = placement.box;
1854
+ if (box.x < 0) score += Math.abs(box.x) * 300;
1855
+ if (box.y < 0) score += Math.abs(box.y) * 300;
1856
+ if (box.x + box.w > laid.width) score += (box.x + box.w - laid.width) * 300;
1857
+ if (box.y + box.h > laid.height) score += (box.y + box.h - laid.height) * 300;
1858
+ for (const node of laid.nodes) {
1859
+ if (node.isContainer) {
1860
+ const title = { x: node.x, y: node.y, w: node.w, h: 6 };
1861
+ score += rectOverlapArea(box, title) * 900;
1862
+ score += rectBorderOverlapPenalty(box, node) * 1800;
1863
+ score += rectBorderBandOverlapArea(box, node, 1.8) * 9e3;
1864
+ } else {
1865
+ const bodyOverlap = rectOverlapArea(box, node);
1866
+ if (bodyOverlap > 0) score += 1e6 + bodyOverlap * 5e4;
1867
+ score += rectOverlapArea(box, expandBox(node, 1.8)) * 5200;
1868
+ }
1869
+ }
1870
+ for (const other of laid.edges) {
1871
+ for (let i = 0; i < other.points.length - 1; i++) {
1872
+ const a = other.points[i];
1873
+ const b = other.points[i + 1];
1874
+ const overlap = segmentIntersectsRect(a, b, expandBox(box, 0.8));
1875
+ if (!overlap) continue;
1876
+ score += other === edge ? 35 : 380;
1877
+ }
1878
+ }
1879
+ for (const occupied of occupiedLabels) {
1880
+ score += rectOverlapArea(box, occupied) * 18e3;
1881
+ }
1882
+ const segmentLen = Math.abs(placement.lineA[0] - placement.lineB[0]) + Math.abs(placement.lineA[1] - placement.lineB[1]);
1883
+ const labelSpan = placement.vertical ? box.h : box.w;
1884
+ score += Math.max(0, Math.max(36, labelSpan * 2.4) - segmentLen) * 80;
1885
+ const center = placement.vertical ? box.y + box.h / 2 : box.x + box.w / 2;
1886
+ const start = placement.vertical ? placement.lineA[1] : placement.lineA[0];
1887
+ const end = placement.vertical ? placement.lineB[1] : placement.lineB[0];
1888
+ const endpointDistance = Math.min(Math.abs(center - start), Math.abs(center - end));
1889
+ score += Math.max(0, 12 - endpointDistance) * 80;
1890
+ return score;
1891
+ }
1892
+ function estimateTextWidth(text, fontSize) {
1893
+ let width = 0;
1894
+ for (const char of text) {
1895
+ if (char === " ") width += fontSize * 0.35;
1896
+ else if (char.charCodeAt(0) <= 127) width += fontSize * 0.58;
1897
+ else width += fontSize;
1898
+ }
1899
+ return Math.max(fontSize * 2, width);
1900
+ }
1901
+ function longestSegment(pts) {
1902
+ let best = 0;
1903
+ let bestLen = -1;
1904
+ for (let i = 0; i < pts.length - 1; i++) {
1905
+ const dx = pts[i + 1][0] - pts[i][0];
1906
+ const dy = pts[i + 1][1] - pts[i][1];
1907
+ const len = dx * dx + dy * dy;
1908
+ if (len > bestLen) {
1909
+ bestLen = len;
1910
+ best = i;
1911
+ }
1912
+ }
1913
+ const a = pts[best];
1914
+ const b = pts[best + 1];
1915
+ return [a, b];
1916
+ }
1917
+ function expandBox(box, pad) {
1918
+ return { x: box.x - pad, y: box.y - pad, w: box.w + pad * 2, h: box.h + pad * 2 };
1919
+ }
1920
+ function rectOverlapArea(a, b) {
1921
+ const x = Math.max(0, Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x));
1922
+ const y = Math.max(0, Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y));
1923
+ return x * y;
1924
+ }
1925
+ function rectBorderOverlapPenalty(a, b) {
1926
+ const nearLeft = Math.abs(a.x - b.x) < 1 || Math.abs(a.x + a.w - b.x) < 1;
1927
+ const nearRight = Math.abs(a.x - (b.x + b.w)) < 1 || Math.abs(a.x + a.w - (b.x + b.w)) < 1;
1928
+ const nearTop = Math.abs(a.y - b.y) < 1 || Math.abs(a.y + a.h - b.y) < 1;
1929
+ const nearBottom = Math.abs(a.y - (b.y + b.h)) < 1 || Math.abs(a.y + a.h - (b.y + b.h)) < 1;
1930
+ return Number(nearLeft || nearRight || nearTop || nearBottom);
1931
+ }
1932
+ function rectBorderBandOverlapArea(a, b, band) {
1933
+ 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 });
1934
+ }
1935
+ function segmentIntersectsRect(a, b, box) {
1936
+ const left = box.x;
1937
+ const right = box.x + box.w;
1938
+ const top = box.y;
1939
+ const bottom = box.y + box.h;
1940
+ if (Math.abs(a[0] - b[0]) < EPS2) {
1941
+ const x = a[0];
1942
+ if (x <= left || x >= right) return false;
1943
+ return intervalOverlap2(a[1], b[1], top, bottom) > EPS2;
1944
+ }
1945
+ if (Math.abs(a[1] - b[1]) < EPS2) {
1946
+ const y = a[1];
1947
+ if (y <= top || y >= bottom) return false;
1948
+ return intervalOverlap2(a[0], b[0], left, right) > EPS2;
1949
+ }
1950
+ return false;
1951
+ }
1952
+ function intervalOverlap2(a1, a2, b1, b2) {
1953
+ const minA = Math.min(a1, a2);
1954
+ const maxA = Math.max(a1, a2);
1955
+ const minB = Math.min(b1, b2);
1956
+ const maxB = Math.max(b1, b2);
1957
+ return Math.max(0, Math.min(maxA, maxB) - Math.max(minA, minB));
1958
+ }
1959
+
1960
+ // src/core/refs.ts
1961
+ function refsToMarkdown(doc) {
1962
+ const ids = sortedIds(doc);
1963
+ const lines = [];
1964
+ lines.push("## \u7B26\u53F7\u306E\u8AAC\u660E / Reference Signs");
1965
+ lines.push("");
1966
+ lines.push("| \u7B26\u53F7 | \u540D\u79F0(\u65E5\u672C\u8A9E) | Name (English) |");
1967
+ lines.push("|------|---------------|----------------|");
1968
+ for (const id of ids) {
1969
+ const n = doc.nodes.get(id);
1970
+ lines.push(`| ${id} | ${n.label.ja ?? ""} | ${n.label.en ?? ""} |`);
1971
+ }
1972
+ return lines.join("\n");
1973
+ }
1974
+ function refsToCsv(doc) {
1975
+ const ids = sortedIds(doc);
1976
+ const lines = ["id,ja,en"];
1977
+ for (const id of ids) {
1978
+ const n = doc.nodes.get(id);
1979
+ lines.push(`${csv(id)},${csv(n.label.ja ?? "")},${csv(n.label.en ?? "")}`);
1980
+ }
1981
+ return lines.join("\n");
1982
+ }
1983
+ function csv(s) {
1984
+ return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
1985
+ }
1986
+ function sortedIds(doc) {
1987
+ const ids = [...doc.nodes.keys()].filter((id) => id !== "*");
1988
+ return ids.sort((a, b) => {
1989
+ const na = parseInt(a, 10);
1990
+ const nb = parseInt(b, 10);
1991
+ if (!isNaN(na) && !isNaN(nb)) return na - nb;
1992
+ return a.localeCompare(b);
1993
+ });
1994
+ }
1995
+
1996
+ // src/core/samples.ts
1997
+ var SAMPLE_ORDER = [
1998
+ "block",
1999
+ "system",
2000
+ "iot",
2001
+ "imagePipeline",
2002
+ "controlLoop",
2003
+ "flow",
2004
+ "state",
2005
+ "seq",
2006
+ "handshake"
2007
+ ];
2008
+ var SAMPLES = {
2009
+ block: {
2010
+ label: "\u30D6\u30ED\u30C3\u30AF\u56F3",
2011
+ hint: "\u300C:\u300D\u3092\u4F7F\u3046\u3068\u30D6\u30ED\u30C3\u30AF\u56F3\u306B\u306A\u308B",
2012
+ source: `# \u30D6\u30ED\u30C3\u30AF\u56F3(\u88C5\u7F6E\u30AF\u30EC\u30FC\u30E0\u7528)
2013
+ # \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
2014
+
2015
+ 10 = \u5236\u5FA1\u88C5\u7F6E / control device
2016
+ 11 = CPU
2017
+ 12 = \u30E1\u30E2\u30EA / memory
2018
+ 13 = "I/O \u30A4\u30F3\u30BF\u30FC\u30D5\u30A7\u30FC\u30B9" / "I/O interface"
2019
+ 20 = \u5916\u90E8\u6A5F\u5668 / external device
2020
+
2021
+ 10 : 11 12 13
2022
+
2023
+ 11 - 12
2024
+ 11 - 13
2025
+ 13 -> 20 : \u4FE1\u53F7 / signal
2026
+ `
2027
+ },
2028
+ system: {
2029
+ label: "\u30B7\u30B9\u30C6\u30E0\u5168\u4F53",
2030
+ hint: "\u968E\u5C64\u69CB\u6210 + \u5916\u90E8\u63A5\u7D9A",
2031
+ source: `# \u30B7\u30B9\u30C6\u30E0\u5168\u4F53(\u968E\u5C64+\u5916\u90E8\u63A5\u7D9A)
2032
+ # \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
2033
+
2034
+ 100 = \u30B7\u30B9\u30C6\u30E0\u672C\u4F53 / Main system
2035
+ 10 = \u5236\u5FA1\u90E8 / Control
2036
+ 20 = \u901A\u4FE1\u90E8 / Comm
2037
+ 11 = CPU
2038
+ 12 = \u30E1\u30E2\u30EA / memory
2039
+ 21 = \u7121\u7DDA\u90E8 / wireless
2040
+ 22 = \u6709\u7DDA\u90E8 / wired
2041
+ 30 = \u5916\u90E8\u30B5\u30FC\u30D0 / external server
2042
+ 40 = \u5916\u90E8\u7AEF\u672B / external terminal
2043
+
2044
+ 100 : 10 20
2045
+ 10 : 11 12
2046
+ 20 : 21 22
2047
+
2048
+ 21 .> 40 : \u7121\u7DDA / wireless
2049
+ 22 -> 30 : \u6709\u7DDA / wired
2050
+ 30 <-> 40 : \u901A\u4FE1 / comm
2051
+ `
2052
+ },
2053
+ iot: {
2054
+ label: "IoT/\u30AF\u30E9\u30A6\u30C9",
2055
+ hint: "\u30BB\u30F3\u30B5\u7AEF\u672B\u30FB\u30B2\u30FC\u30C8\u30A6\u30A7\u30A4\u30FB\u30AF\u30E9\u30A6\u30C9\u306E\u5178\u578B\u69CB\u6210",
2056
+ source: `# IoT/\u30AF\u30E9\u30A6\u30C9\u69CB\u6210
2057
+ # \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
2058
+
2059
+ 100 = \u30BB\u30F3\u30B5\u7AEF\u672B / sensor terminal
2060
+ 10 = \u691C\u51FA\u90E8 / detector
2061
+ 11 = \u6E29\u5EA6\u30BB\u30F3\u30B5 / temperature sensor
2062
+ 12 = \u52A0\u901F\u5EA6\u30BB\u30F3\u30B5 / acceleration sensor
2063
+ 20 = \u51E6\u7406\u90E8 / processor
2064
+ 21 = \u53D6\u5F97\u90E8 / acquisition unit
2065
+ 22 = \u5224\u5B9A\u90E8 / determination unit
2066
+ 30 = \u901A\u4FE1\u90E8 / communication unit
2067
+ 200 = \u30B2\u30FC\u30C8\u30A6\u30A7\u30A4 / gateway
2068
+ 300 = \u30AF\u30E9\u30A6\u30C9\u30B5\u30FC\u30D0 / cloud server
2069
+ 310 = \u53D7\u4FE1\u90E8 / receiver
2070
+ 320 = \u89E3\u6790\u90E8 / analyzer
2071
+ 330 = \u8A18\u61B6\u90E8 / storage
2072
+
2073
+ 100 : 10 20 30
2074
+ 10 : 11 12
2075
+ 20 : 21 22
2076
+ 300 : 310 320 330
2077
+
2078
+ 10 -> 20 : \u30BB\u30F3\u30B5\u5024 / sensor value
2079
+ 20 -> 30 : \u9001\u4FE1\u30C7\u30FC\u30BF / transmission data
2080
+ 30 .> 200 : \u7121\u7DDA / wireless
2081
+ 200 -> 310 : \u4E2D\u7D99 / relay
2082
+ 310 -> 320 : \u30C7\u30FC\u30BF / data
2083
+ 320 -> 330 : \u7D50\u679C / result
2084
+ `
2085
+ },
2086
+ imagePipeline: {
2087
+ label: "\u753B\u50CF\u51E6\u7406",
2088
+ hint: "\u753B\u50CF\u5165\u529B\u304B\u3089\u5224\u5B9A\u7D50\u679C\u307E\u3067\u306E\u51E6\u7406\u30D1\u30A4\u30D7\u30E9\u30A4\u30F3",
2089
+ source: `# \u753B\u50CF\u51E6\u7406\u30D1\u30A4\u30D7\u30E9\u30A4\u30F3(\u65B9\u6CD5\u30AF\u30EC\u30FC\u30E0\u7528)
2090
+ # \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
2091
+
2092
+ S100 = \u753B\u50CF\u3092\u53D6\u5F97 / Acquire image
2093
+ S110 = \u524D\u51E6\u7406 / Preprocess
2094
+ S120 = \u7279\u5FB4\u91CF\u3092\u62BD\u51FA / Extract features
2095
+ S130 = \u6B20\u9665\u3042\u308A? / Defect?
2096
+ S140 = \u30A2\u30E9\u30FC\u30C8\u3092\u51FA\u529B / Output alert
2097
+ S150 = \u6B63\u5E38\u7D50\u679C\u3092\u8A18\u9332 / Record normal result
2098
+ S160 = \u7D42\u4E86 / End
2099
+
2100
+ S100 -> S110
2101
+ S110 -> S120
2102
+ S120 -> S130
2103
+ S130 -> S140 : Yes
2104
+ S130 -> S150 : No
2105
+ S140 -> S160
2106
+ S150 -> S160
2107
+ `
2108
+ },
2109
+ controlLoop: {
2110
+ label: "\u5236\u5FA1\u30EB\u30FC\u30D7",
2111
+ 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",
2112
+ source: `# \u5236\u5FA1\u30EB\u30FC\u30D7(\u88C5\u7F6E\u30AF\u30EC\u30FC\u30E0\u7528)
2113
+ # \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
2114
+
2115
+ 100 = \u5236\u5FA1\u30B7\u30B9\u30C6\u30E0 / control system
2116
+ 10 = \u5236\u5FA1\u90E8 / controller
2117
+ 11 = \u76EE\u6A19\u5024\u53D6\u5F97\u90E8 / target acquisition unit
2118
+ 12 = \u504F\u5DEE\u7B97\u51FA\u90E8 / error calculator
2119
+ 13 = \u6307\u4EE4\u751F\u6210\u90E8 / command generator
2120
+ 20 = \u99C6\u52D5\u90E8 / driver
2121
+ 30 = \u30BB\u30F3\u30B5\u90E8 / sensor unit
2122
+ 40 = \u5BFE\u8C61\u88C5\u7F6E / controlled object
2123
+
2124
+ 100 : 30 10 40 20
2125
+ 10 : 11 12 13
2126
+
2127
+ 30 -> 12 : \u6E2C\u5B9A\u5024 / measured value
2128
+ 11 -> 12 : \u76EE\u6A19\u5024 / target value
2129
+ 12 -> 13 : \u504F\u5DEE / error
2130
+ 13 -> 20 : \u6307\u4EE4 / command
2131
+ 20 -> 40 : \u99C6\u52D5\u4FE1\u53F7 / drive signal
2132
+ 40 .> 30 : \u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF / feedback
2133
+ `
2134
+ },
2135
+ flow: {
2136
+ label: "\u30D5\u30ED\u30FC\u30C1\u30E3\u30FC\u30C8",
2137
+ hint: "\u30E9\u30D9\u30EB\u306B\u300C?\u300D\u304C\u3042\u308B \u2192 \u30D5\u30ED\u30FC\u3068\u5224\u5B9A",
2138
+ source: `# \u30D5\u30ED\u30FC\u30C1\u30E3\u30FC\u30C8(\u65B9\u6CD5\u30AF\u30EC\u30FC\u30E0\u7528)
2139
+ # \u30D2\u30F3\u30C8:\u30E9\u30D9\u30EB\u672B\u5C3E\u306B\u300C?\u300D \u2192 \u83F1\u5F62(\u6761\u4EF6\u5206\u5C90)\u306B\u81EA\u52D5\u63A8\u8AD6
2140
+
2141
+ S100 = \u958B\u59CB / Start
2142
+ S110 = \u6761\u4EF6A? / "Condition A?"
2143
+ S120 = \u51E6\u7406X / Process X
2144
+ S130 = \u51E6\u7406Y / Process Y
2145
+ S140 = \u7D42\u4E86 / End
2146
+
2147
+ S100 -> S110
2148
+ S110 -> S120 : Yes
2149
+ S110 -> S130 : No
2150
+ S120 -> S140
2151
+ S130 -> S140
2152
+ `
2153
+ },
2154
+ state: {
2155
+ label: "\u72B6\u614B\u9077\u79FB\u56F3",
2156
+ hint: "\u300C*\u300D\u3092\u4F7F\u3046 \u2192 \u72B6\u614B\u9077\u79FB\u3068\u5224\u5B9A",
2157
+ source: `# \u72B6\u614B\u9077\u79FB\u56F3
2158
+ # \u30D2\u30F3\u30C8:\u300C*\u300D\u304C\u521D\u671F/\u7D42\u7AEF\u306E\u7B26\u53F7(\u9ED2\u4E38\u3067\u63CF\u753B)
2159
+
2160
+ S1 = \u5F85\u6A5F / Idle
2161
+ S2 = \u52D5\u4F5C\u4E2D / Running
2162
+ S3 = \u30A8\u30E9\u30FC / Error
2163
+
2164
+ * -> S1
2165
+ S1 -> S2 : \u8D77\u52D5 / start
2166
+ S2 -> S1 : \u505C\u6B62 / stop
2167
+ S2 -> S3 : \u7570\u5E38 / fault
2168
+ S3 -> S1 : \u30EA\u30BB\u30C3\u30C8 / reset
2169
+ S3 -> *
2170
+ `
2171
+ },
2172
+ seq: {
2173
+ label: "\u30B7\u30FC\u30B1\u30F3\u30B9\u56F3",
2174
+ hint: "\u300C:\u300D\u3082\u300C?\u300D\u3082\u300C*\u300D\u3082\u306A\u3044 \u2192 \u30B7\u30FC\u30B1\u30F3\u30B9",
2175
+ source: `# \u30B7\u30FC\u30B1\u30F3\u30B9\u56F3(\u30D7\u30ED\u30C8\u30B3\u30EB\u7CFB)
2176
+ # \u30D2\u30F3\u30C8:\u5305\u542B\u3082?\u3082*\u3082\u7121\u3044 \u2192 \u30B7\u30FC\u30B1\u30F3\u30B9\u56F3\u3068\u3057\u3066\u63CF\u753B
2177
+ # \u30A2\u30AF\u30BF\u306F\u767B\u5834\u9806\u306B\u5DE6\u304B\u3089\u4E26\u3076
2178
+
2179
+ 100 = \u30AF\u30E9\u30A4\u30A2\u30F3\u30C8 / client
2180
+ 200 = \u30B5\u30FC\u30D0 / server
2181
+
2182
+ 100 -> 200 : \u8A8D\u8A3C\u8981\u6C42 / auth request
2183
+ 200 -> 100 : \u30C8\u30FC\u30AF\u30F3 / token
2184
+ 100 -> 200 : \u30EA\u30BD\u30FC\u30B9\u8981\u6C42 / resource request
2185
+ 200 -> 100 : \u30EA\u30BD\u30FC\u30B9\u5FDC\u7B54 / resource response
2186
+ `
2187
+ },
2188
+ handshake: {
2189
+ label: "\u30CF\u30F3\u30C9\u30B7\u30A7\u30A4\u30AF",
2190
+ hint: "\u8981\u6C42/\u5FDC\u7B54/\u78BA\u7ACB/\u7D42\u4E86\u306E\u901A\u4FE1\u30B7\u30FC\u30B1\u30F3\u30B9",
2191
+ source: `# \u901A\u4FE1\u30CF\u30F3\u30C9\u30B7\u30A7\u30A4\u30AF(\u30B7\u30FC\u30B1\u30F3\u30B9\u56F3)
2192
+ # \u30D2\u30F3\u30C8: \u5F80\u5FA9\u30E1\u30C3\u30BB\u30FC\u30B8\u304C\u3042\u308B\u3068\u30B7\u30FC\u30B1\u30F3\u30B9\u56F3\u306B\u306A\u308B
2193
+
2194
+ 100 = \u7AEF\u672B / terminal
2195
+ 200 = \u30B5\u30FC\u30D0 / server
2196
+
2197
+ 100 -> 200 : \u63A5\u7D9A\u8981\u6C42 / connect request
2198
+ 200 -> 100 : \u5FDC\u7B54 / response
2199
+ 100 -> 200 : \u8A8D\u8A3C\u60C5\u5831 / credentials
2200
+ 200 -> 100 : \u8A8D\u8A3C\u7D50\u679C / auth result
2201
+ 100 <-> 200 : \u30C7\u30FC\u30BF\u901A\u4FE1 / data exchange
2202
+ 100 -> 200 : \u5207\u65AD\u8981\u6C42 / disconnect
2203
+ 200 -> 100 : \u5207\u65AD\u5B8C\u4E86 / disconnected
2204
+ `
2205
+ }
2206
+ };
2207
+
2208
+ // src/node/index.ts
2209
+ import { readFileSync as readFileSync2 } from "fs";
2210
+
2211
+ // src/node/dom.ts
2212
+ var SVG_NS = "http://www.w3.org/2000/svg";
2213
+ var ShimElement = class {
2214
+ constructor(namespaceURI, tagName) {
2215
+ this.namespaceURI = namespaceURI;
2216
+ this.tagName = tagName;
2217
+ }
2218
+ namespaceURI;
2219
+ tagName;
2220
+ nodeType = "element";
2221
+ attrs = /* @__PURE__ */ new Map();
2222
+ children = [];
2223
+ text = null;
2224
+ setAttribute(name, value) {
2225
+ this.attrs.set(name, String(value));
2226
+ }
2227
+ getAttribute(name) {
2228
+ return this.attrs.has(name) ? this.attrs.get(name) : null;
2229
+ }
2230
+ removeAttribute(name) {
2231
+ this.attrs.delete(name);
2232
+ }
2233
+ appendChild(child) {
2234
+ this.text = null;
2235
+ this.children.push(child);
2236
+ return child;
2237
+ }
2238
+ set textContent(value) {
2239
+ this.text = value == null ? "" : String(value);
2240
+ this.children = [];
2241
+ }
2242
+ get textContent() {
2243
+ if (this.text != null) return this.text;
2244
+ return this.children.map((c) => c.textContent).join("");
2245
+ }
2246
+ };
2247
+ var shimDocument = {
2248
+ createElementNS(ns, qualifiedName) {
2249
+ return new ShimElement(ns, qualifiedName);
2250
+ },
2251
+ createElement(tagName) {
2252
+ return new ShimElement(SVG_NS, tagName);
2253
+ }
2254
+ };
2255
+ function withShimDocument(fn) {
2256
+ const g = globalThis;
2257
+ const prev = g.document;
2258
+ g.document = shimDocument;
2259
+ try {
2260
+ return fn();
2261
+ } finally {
2262
+ g.document = prev;
2263
+ }
2264
+ }
2265
+ function escapeAttr(value) {
2266
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2267
+ }
2268
+ function escapeText(value) {
2269
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2270
+ }
2271
+ function serializeSvg(node) {
2272
+ const attrs = [...node.attrs].map(([name, value]) => ` ${name}="${escapeAttr(value)}"`).join("");
2273
+ if (node.text != null) {
2274
+ return `<${node.tagName}${attrs}>${escapeText(node.text)}</${node.tagName}>`;
2275
+ }
2276
+ if (node.children.length === 0) {
2277
+ return `<${node.tagName}${attrs}/>`;
2278
+ }
2279
+ const inner = node.children.map(serializeSvg).join("");
2280
+ return `<${node.tagName}${attrs}>${inner}</${node.tagName}>`;
2281
+ }
2282
+
2283
+ // src/node/content-box.ts
2284
+ var DEFAULT_BLEED = 3;
2285
+ function num(node, name, fallback = 0) {
2286
+ const raw = node.getAttribute(name);
2287
+ if (raw == null) return fallback;
2288
+ const n = Number.parseFloat(raw);
2289
+ return Number.isFinite(n) ? n : fallback;
2290
+ }
2291
+ function parseNumbers(text) {
2292
+ const out = [];
2293
+ const re = /-?\d*\.?\d+(?:e[-+]?\d+)?/gi;
2294
+ let m;
2295
+ while ((m = re.exec(text)) !== null) out.push(Number.parseFloat(m[0]));
2296
+ return out;
2297
+ }
2298
+ function primitiveBounds(node) {
2299
+ switch (node.tagName) {
2300
+ case "rect": {
2301
+ const x = num(node, "x");
2302
+ const y = num(node, "y");
2303
+ const w = num(node, "width");
2304
+ const h = num(node, "height");
2305
+ if (w <= 0 && h <= 0) return null;
2306
+ return { minX: x, minY: y, maxX: x + w, maxY: y + h };
2307
+ }
2308
+ case "circle": {
2309
+ const cx = num(node, "cx");
2310
+ const cy = num(node, "cy");
2311
+ const r = num(node, "r");
2312
+ if (r <= 0) return null;
2313
+ return { minX: cx - r, minY: cy - r, maxX: cx + r, maxY: cy + r };
2314
+ }
2315
+ case "line": {
2316
+ const x1 = num(node, "x1");
2317
+ const y1 = num(node, "y1");
2318
+ const x2 = num(node, "x2");
2319
+ const y2 = num(node, "y2");
2320
+ return {
2321
+ minX: Math.min(x1, x2),
2322
+ minY: Math.min(y1, y2),
2323
+ maxX: Math.max(x1, x2),
2324
+ maxY: Math.max(y1, y2)
2325
+ };
2326
+ }
2327
+ case "polygon":
2328
+ case "polyline": {
2329
+ const nums = parseNumbers(node.getAttribute("points") ?? "");
2330
+ return boundsFromPairs(nums);
2331
+ }
2332
+ case "path": {
2333
+ const nums = parseNumbers(node.getAttribute("d") ?? "");
2334
+ return boundsFromPairs(nums);
2335
+ }
2336
+ case "text": {
2337
+ return textBounds(node);
2338
+ }
2339
+ default:
2340
+ return null;
2341
+ }
2342
+ }
2343
+ function boundsFromPairs(nums) {
2344
+ if (nums.length < 2) return null;
2345
+ let minX = Infinity;
2346
+ let minY = Infinity;
2347
+ let maxX = -Infinity;
2348
+ let maxY = -Infinity;
2349
+ for (let i = 0; i + 1 < nums.length; i += 2) {
2350
+ const x = nums[i];
2351
+ const y = nums[i + 1];
2352
+ if (x < minX) minX = x;
2353
+ if (y < minY) minY = y;
2354
+ if (x > maxX) maxX = x;
2355
+ if (y > maxY) maxY = y;
2356
+ }
2357
+ if (!Number.isFinite(minX)) return null;
2358
+ return { minX, minY, maxX, maxY };
2359
+ }
2360
+ function textBounds(node) {
2361
+ const text = node.textContent;
2362
+ if (!text) return null;
2363
+ const x = num(node, "x");
2364
+ const y = num(node, "y");
2365
+ const fontSize = num(node, "font-size", 2.8);
2366
+ const width = estimateTextWidth(text, fontSize);
2367
+ const anchor = node.getAttribute("text-anchor") ?? "start";
2368
+ let minX;
2369
+ if (anchor === "middle") minX = x - width / 2;
2370
+ else if (anchor === "end") minX = x - width;
2371
+ else minX = x;
2372
+ const maxX = minX + width;
2373
+ const baseline = node.getAttribute("dominant-baseline") ?? "alphabetic";
2374
+ let minY;
2375
+ let maxY;
2376
+ if (baseline === "middle" || baseline === "central") {
2377
+ minY = y - fontSize * 0.6;
2378
+ maxY = y + fontSize * 0.6;
2379
+ } else {
2380
+ minY = y - fontSize * 0.8;
2381
+ maxY = y + fontSize * 0.25;
2382
+ }
2383
+ return { minX, minY, maxX, maxY };
2384
+ }
2385
+ function collectBounds(node, acc) {
2386
+ const b = primitiveBounds(node);
2387
+ if (b) acc.push(b);
2388
+ for (const child of node.children) collectBounds(child, acc);
2389
+ }
2390
+ function unionAll(bounds) {
2391
+ if (bounds.length === 0) return null;
2392
+ let minX = Infinity;
2393
+ let minY = Infinity;
2394
+ let maxX = -Infinity;
2395
+ let maxY = -Infinity;
2396
+ for (const b of bounds) {
2397
+ if (b.minX < minX) minX = b.minX;
2398
+ if (b.minY < minY) minY = b.minY;
2399
+ if (b.maxX > maxX) maxX = b.maxX;
2400
+ if (b.maxY > maxY) maxY = b.maxY;
2401
+ }
2402
+ return { minX, minY, maxX, maxY };
2403
+ }
2404
+ function fallbackViewBox(root) {
2405
+ const nums = parseNumbers(root.getAttribute("viewBox") ?? "");
2406
+ if (nums.length === 4 && nums[2] > 0 && nums[3] > 0) {
2407
+ return { minX: nums[0], minY: nums[1], width: nums[2], height: nums[3] };
2408
+ }
2409
+ return { minX: 0, minY: 0, width: 210, height: 297 };
2410
+ }
2411
+ function computeContentBox(root, bleed = DEFAULT_BLEED) {
2412
+ const all = [];
2413
+ collectBounds(root, all);
2414
+ const merged = unionAll(all);
2415
+ if (!merged) return fallbackViewBox(root);
2416
+ const pad = Math.max(0, bleed);
2417
+ return {
2418
+ minX: merged.minX - pad,
2419
+ minY: merged.minY - pad,
2420
+ width: Math.max(1, merged.maxX - merged.minX + pad * 2),
2421
+ height: Math.max(1, merged.maxY - merged.minY + pad * 2)
2422
+ };
2423
+ }
2424
+ function svgDisplayDimensions(viewBox, targetSide = 1600) {
2425
+ const longest = Math.max(viewBox.width, viewBox.height, 1);
2426
+ const scale = Math.max(1, targetSide / longest);
2427
+ return {
2428
+ width: Math.max(1, Math.ceil(viewBox.width * scale)),
2429
+ height: Math.max(1, Math.ceil(viewBox.height * scale)),
2430
+ scale
2431
+ };
2432
+ }
2433
+ var RASTER_SCALE = 8;
2434
+ var RASTER_MAX_SIDE = 24e3;
2435
+ var RASTER_MAX_PIXELS = 1e8;
2436
+ function rasterDimensions(viewBox, scale = RASTER_SCALE) {
2437
+ const requested = Number.isFinite(scale) && scale > 0 ? scale : RASTER_SCALE;
2438
+ const sideScale = RASTER_MAX_SIDE / Math.max(viewBox.width, viewBox.height, 1);
2439
+ const pixelScale = Math.sqrt(RASTER_MAX_PIXELS / Math.max(viewBox.width * viewBox.height, 1));
2440
+ const capped = Math.min(requested, sideScale, pixelScale);
2441
+ return {
2442
+ width: Math.max(1, Math.ceil(viewBox.width * capped)),
2443
+ height: Math.max(1, Math.ceil(viewBox.height * capped)),
2444
+ scale: capped
2445
+ };
2446
+ }
2447
+
2448
+ // src/node/svg.ts
2449
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
2450
+ var DEFAULT_BLEED2 = 3;
2451
+ var DEFAULT_TARGET_SIDE = 1600;
2452
+ function buildSvgModel(source, opts = {}) {
2453
+ const {
2454
+ lang = "ja",
2455
+ crop = true,
2456
+ bleed = DEFAULT_BLEED2,
2457
+ targetSide = DEFAULT_TARGET_SIDE
2458
+ } = opts;
2459
+ const doc = parse(source);
2460
+ const laid = layout(doc);
2461
+ const svgEl = withShimDocument(() => render(laid, { lang }));
2462
+ const viewBox = crop ? computeContentBox(svgEl, bleed) : { minX: 0, minY: 0, width: laid.width, height: laid.height };
2463
+ const display = svgDisplayDimensions(viewBox, targetSide);
2464
+ svgEl.setAttribute("xmlns", SVG_NS2);
2465
+ svgEl.setAttribute("version", "1.1");
2466
+ svgEl.setAttribute(
2467
+ "viewBox",
2468
+ `${viewBox.minX} ${viewBox.minY} ${viewBox.width} ${viewBox.height}`
2469
+ );
2470
+ svgEl.setAttribute("width", String(display.width));
2471
+ svgEl.setAttribute("height", String(display.height));
2472
+ svgEl.setAttribute("preserveAspectRatio", "xMidYMid meet");
2473
+ return { el: svgEl, kind: doc.kind, viewBox, width: display.width, height: display.height };
2474
+ }
2475
+ function renderToSvg(source, opts = {}) {
2476
+ const model = buildSvgModel(source, opts);
2477
+ const body = serializeSvg(model.el);
2478
+ const svg = opts.xmlDeclaration ?? true ? `<?xml version="1.0" encoding="UTF-8"?>
2479
+ ${body}` : body;
2480
+ return {
2481
+ svg,
2482
+ kind: model.kind,
2483
+ viewBox: model.viewBox,
2484
+ width: model.width,
2485
+ height: model.height
2486
+ };
2487
+ }
2488
+
2489
+ // src/node/assets.ts
2490
+ import { existsSync, readFileSync } from "fs";
2491
+ import { dirname, join } from "path";
2492
+ import { fileURLToPath } from "url";
2493
+ var FONT_FAMILY_NAME = "IPAexGothic";
2494
+ var cachedPath = null;
2495
+ var cachedBuffer = null;
2496
+ function moduleDir() {
2497
+ let url = "";
2498
+ try {
2499
+ url = import.meta.url;
2500
+ } catch {
2501
+ }
2502
+ if (url) {
2503
+ try {
2504
+ return dirname(fileURLToPath(url));
2505
+ } catch {
2506
+ }
2507
+ }
2508
+ if (typeof __dirname !== "undefined") return __dirname;
2509
+ return process.cwd();
2510
+ }
2511
+ function resolvePackageFile(...segments) {
2512
+ let dir = moduleDir();
2513
+ for (let i = 0; i < 8; i++) {
2514
+ const candidate = join(dir, ...segments);
2515
+ if (existsSync(candidate)) return candidate;
2516
+ const parent = dirname(dir);
2517
+ if (parent === dir) break;
2518
+ dir = parent;
2519
+ }
2520
+ throw new Error(
2521
+ `pdgkit: bundled file ${segments.join("/")} not found. Ensure the package was installed with its data files intact.`
2522
+ );
2523
+ }
2524
+ function resolveFontPath() {
2525
+ if (cachedPath) return cachedPath;
2526
+ cachedPath = resolvePackageFile("assets", "ipaexg.ttf");
2527
+ return cachedPath;
2528
+ }
2529
+ function loadFontBuffer() {
2530
+ if (cachedBuffer) return cachedBuffer;
2531
+ cachedBuffer = readFileSync(resolveFontPath());
2532
+ return cachedBuffer;
2533
+ }
2534
+ function loadFontBase64() {
2535
+ return loadFontBuffer().toString("base64");
2536
+ }
2537
+
2538
+ // src/node/validate.ts
2539
+ var KIND_LABEL_JA = {
2540
+ block: "\u30D6\u30ED\u30C3\u30AF\u56F3",
2541
+ flow: "\u30D5\u30ED\u30FC\u30C1\u30E3\u30FC\u30C8",
2542
+ state: "\u72B6\u614B\u9077\u79FB\u56F3",
2543
+ seq: "\u30B7\u30FC\u30B1\u30F3\u30B9\u56F3"
2544
+ };
2545
+ function commentOf(line) {
2546
+ let inQuote = false;
2547
+ for (let i = 0; i < line.length; i++) {
2548
+ const c = line[i];
2549
+ if (c === '"') inQuote = !inQuote;
2550
+ else if (!inQuote && c === "#") return line.slice(i + 1);
2551
+ }
2552
+ return null;
2553
+ }
2554
+ function uncommented(line) {
2555
+ let inQuote = false;
2556
+ for (let i = 0; i < line.length; i++) {
2557
+ const c = line[i];
2558
+ if (c === '"') inQuote = !inQuote;
2559
+ else if (!inQuote && c === "#") return line.slice(0, i);
2560
+ }
2561
+ return line;
2562
+ }
2563
+ var KIND_DIRECTIVE_RE = /^\s*!?\s*(?:pdgkit\s*:)?\s*kind\s*[:=]?\s*(block|flow|state|seq)\b/i;
2564
+ function findKindDirective(lines) {
2565
+ for (let i = 0; i < lines.length; i++) {
2566
+ const comment = commentOf(lines[i]);
2567
+ if (comment == null) continue;
2568
+ const m = comment.match(KIND_DIRECTIVE_RE);
2569
+ if (m) return { kind: m[1].toLowerCase(), line: i + 1 };
2570
+ }
2571
+ return null;
2572
+ }
2573
+ var SPACED_OP_RE = /(?:^|\s)(?:<->|=>|->|<-|\.>|\.\.|-)(?:\s|$)/g;
2574
+ var GLUED_OP_RE = /[A-Za-z0-9_*](?:<->|<-|->|=>|\.>|\.\.|-)[A-Za-z0-9_*]/;
2575
+ function countSpacedOps(text) {
2576
+ const matches = text.match(SPACED_OP_RE);
2577
+ return matches ? matches.length : 0;
2578
+ }
2579
+ function validate(source) {
2580
+ const doc = parse(source);
2581
+ const diagnostics = [...doc.diagnostics];
2582
+ const lines = source.split(/\r?\n/);
2583
+ const directive = findKindDirective(lines);
2584
+ const declaredKind = directive ? directive.kind : null;
2585
+ let kindMatches = null;
2586
+ if (directive) {
2587
+ kindMatches = directive.kind === doc.kind;
2588
+ if (!kindMatches) {
2589
+ diagnostics.push({
2590
+ severity: "error",
2591
+ line: directive.line,
2592
+ col: 1,
2593
+ message: `\u56F3\u7A2E\u30A2\u30B5\u30FC\u30B7\u30E7\u30F3\u4E0D\u4E00\u81F4: \u5BA3\u8A00=${KIND_LABEL_JA[directive.kind]}(${directive.kind}) \u3060\u304C\u63A8\u8AD6=${KIND_LABEL_JA[doc.kind]}(${doc.kind})\u3002\u69CB\u9020(\u5305\u542B\u300C:\u300D/ \u672B\u5C3E\u300C?\u300D/ \u7B26\u53F7\u300C*\u300D/ \u5F80\u5FA9\u30FB\u300C<->\u300D)\u3092\u898B\u76F4\u3059\u304B\u3001\u5BA3\u8A00\u3092\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
2594
+ });
2595
+ }
2596
+ }
2597
+ const unknownLines = new Set(
2598
+ doc.diagnostics.filter((d) => d.severity === "error" && d.message.startsWith("\u69CB\u6587\u4E0D\u660E")).map((d) => d.line)
2599
+ );
2600
+ for (const lineNum of unknownLines) {
2601
+ const code = uncommented(lines[lineNum - 1] ?? "").trim();
2602
+ if (!code) continue;
2603
+ if (countSpacedOps(code) >= 2) {
2604
+ diagnostics.push({
2605
+ severity: "info",
2606
+ line: lineNum,
2607
+ col: 1,
2608
+ message: "\u9023\u9396\u8A18\u6CD5(A -> B -> C)\u306F\u672A\u5BFE\u5FDC\u3067\u3059\u30021\u884C\u306B1\u63A5\u7D9A\u3067\u5206\u5272\u3057\u3066\u304F\u3060\u3055\u3044(\u4F8B: A -> B / B -> C)\u3002"
2609
+ });
2610
+ } else if (GLUED_OP_RE.test(code)) {
2611
+ diagnostics.push({
2612
+ severity: "info",
2613
+ line: lineNum,
2614
+ col: 1,
2615
+ message: '\u63A5\u7D9A\u6F14\u7B97\u5B50\u306E\u524D\u5F8C\u306B\u306F\u534A\u89D2\u30B9\u30DA\u30FC\u30B9\u304C\u5FC5\u8981\u3067\u3059(\u4F8B: "11-12" \u3067\u306F\u306A\u304F "11 - 12")\u3002'
2616
+ });
2617
+ }
2618
+ }
2619
+ diagnostics.sort((a, b) => a.line - b.line || a.col - b.col);
2620
+ const counts = {
2621
+ errors: diagnostics.filter((d) => d.severity === "error").length,
2622
+ warnings: diagnostics.filter((d) => d.severity === "warning").length,
2623
+ infos: diagnostics.filter((d) => d.severity === "info").length
2624
+ };
2625
+ return {
2626
+ ok: counts.errors === 0,
2627
+ kind: doc.kind,
2628
+ declaredKind,
2629
+ kindMatches,
2630
+ diagnostics,
2631
+ counts
2632
+ };
2633
+ }
2634
+
2635
+ // src/node/raster.ts
2636
+ var JPEG_QUALITY = 100;
2637
+ async function renderPixels(source, opts) {
2638
+ const { lang = "ja", scale = 8, bleed = 3 } = opts;
2639
+ const { svg, viewBox } = renderToSvg(source, { lang, crop: true, bleed });
2640
+ const dims = rasterDimensions(viewBox, scale);
2641
+ const { Resvg } = await import("@resvg/resvg-js");
2642
+ const resvg = new Resvg(svg, {
2643
+ background: "rgba(255,255,255,1)",
2644
+ fitTo: { mode: "width", value: dims.width },
2645
+ font: {
2646
+ fontFiles: [resolveFontPath()],
2647
+ loadSystemFonts: false,
2648
+ defaultFontFamily: FONT_FAMILY_NAME
2649
+ }
2650
+ });
2651
+ const rendered = resvg.render();
2652
+ const raw = {
2653
+ pixels: rendered.pixels,
2654
+ width: rendered.width,
2655
+ height: rendered.height
2656
+ };
2657
+ return { raw, png: rendered.asPng() };
2658
+ }
2659
+ async function renderToPng(source, opts = {}) {
2660
+ const { png } = await renderPixels(source, opts);
2661
+ return png;
2662
+ }
2663
+ async function renderToJpeg(source, opts = {}) {
2664
+ const { raw } = await renderPixels(source, opts);
2665
+ const jpeg = (await import("jpeg-js")).default;
2666
+ const encoded = jpeg.encode(
2667
+ { data: Buffer.from(raw.pixels), width: raw.width, height: raw.height },
2668
+ JPEG_QUALITY
2669
+ );
2670
+ return encoded.data;
2671
+ }
2672
+
2673
+ // src/node/ooxml.ts
2674
+ var PPTX_SLIDE_W = 12192e3;
2675
+ var PPTX_SLIDE_H = 6858e3;
2676
+ var PPTX_MARGIN = 457200;
2677
+ function fitRectIntoPage(sourceW, sourceH, pageW, pageH, margin = 10) {
2678
+ const usableW = Math.max(1, pageW - margin * 2);
2679
+ const usableH = Math.max(1, pageH - margin * 2);
2680
+ const scale = Math.min(usableW / sourceW, usableH / sourceH);
2681
+ const width = sourceW * scale;
2682
+ const height = sourceH * scale;
2683
+ return { x: (pageW - width) / 2, y: (pageH - height) / 2, width, height };
2684
+ }
2685
+ function fitRectIntoSlide(sourceW, sourceH, slideW = PPTX_SLIDE_W, slideH = PPTX_SLIDE_H, margin = PPTX_MARGIN) {
2686
+ return fitRectIntoPage(sourceW, sourceH, slideW, slideH, margin);
2687
+ }
2688
+ function buildPptxPackage(imagePng, imageW, imageH) {
2689
+ const fit = fitRectIntoSlide(imageW, imageH);
2690
+ const iso = (/* @__PURE__ */ new Date()).toISOString();
2691
+ return createStoredZip([
2692
+ ["[Content_Types].xml", contentTypesXml()],
2693
+ ["_rels/.rels", rootRelsXml()],
2694
+ ["docProps/app.xml", appPropsXml()],
2695
+ ["docProps/core.xml", corePropsXml(iso)],
2696
+ ["ppt/presentation.xml", presentationXml()],
2697
+ ["ppt/_rels/presentation.xml.rels", presentationRelsXml()],
2698
+ ["ppt/slides/slide1.xml", slideXml(fit)],
2699
+ ["ppt/slides/_rels/slide1.xml.rels", slideRelsXml()],
2700
+ ["ppt/slideMasters/slideMaster1.xml", slideMasterXml()],
2701
+ ["ppt/slideMasters/_rels/slideMaster1.xml.rels", slideMasterRelsXml()],
2702
+ ["ppt/slideLayouts/slideLayout1.xml", slideLayoutXml()],
2703
+ ["ppt/slideLayouts/_rels/slideLayout1.xml.rels", slideLayoutRelsXml()],
2704
+ ["ppt/theme/theme1.xml", themeXml()],
2705
+ ["ppt/media/image1.png", imagePng]
2706
+ ]);
2707
+ }
2708
+ function buildEditablePptxPackage(svgEl) {
2709
+ const viewBox = computeContentBox(svgEl);
2710
+ const fit = fitRectIntoSlide(viewBox.width, viewBox.height);
2711
+ const shapes = editablePptxShapes(svgEl, viewBox, fit);
2712
+ const iso = (/* @__PURE__ */ new Date()).toISOString();
2713
+ return createStoredZip([
2714
+ ["[Content_Types].xml", contentTypesXmlEditable()],
2715
+ ["_rels/.rels", rootRelsXml()],
2716
+ ["docProps/app.xml", appPropsXml()],
2717
+ ["docProps/core.xml", corePropsXml(iso)],
2718
+ ["ppt/presentation.xml", presentationXml()],
2719
+ ["ppt/_rels/presentation.xml.rels", presentationRelsXml()],
2720
+ ["ppt/slides/slide1.xml", editableSlideXml(shapes)],
2721
+ ["ppt/slides/_rels/slide1.xml.rels", editableSlideRelsXml()],
2722
+ ["ppt/slideMasters/slideMaster1.xml", slideMasterXml()],
2723
+ ["ppt/slideMasters/_rels/slideMaster1.xml.rels", slideMasterRelsXml()],
2724
+ ["ppt/slideLayouts/slideLayout1.xml", slideLayoutXml()],
2725
+ ["ppt/slideLayouts/_rels/slideLayout1.xml.rels", slideLayoutRelsXml()],
2726
+ ["ppt/theme/theme1.xml", themeXml()]
2727
+ ]);
2728
+ }
2729
+ function collectElements(root, tags) {
2730
+ const out = [];
2731
+ const walk = (node) => {
2732
+ if (tags.has(node.tagName.toLowerCase())) out.push(node);
2733
+ for (const child of node.children) walk(child);
2734
+ };
2735
+ walk(root);
2736
+ return out;
2737
+ }
2738
+ function editablePptxShapes(svgEl, viewBox, fit) {
2739
+ const ctx = {
2740
+ nextId: 2,
2741
+ tx: {
2742
+ minX: viewBox.minX,
2743
+ minY: viewBox.minY,
2744
+ scaleX: fit.width / viewBox.width,
2745
+ scaleY: fit.height / viewBox.height,
2746
+ fitX: fit.x,
2747
+ fitY: fit.y
2748
+ }
2749
+ };
2750
+ const out = [];
2751
+ const elements = collectElements(svgEl, /* @__PURE__ */ new Set(["rect", "circle", "polygon", "path", "text", "line", "polyline"]));
2752
+ for (const el2 of elements) {
2753
+ const tag = el2.tagName.toLowerCase();
2754
+ if (tag === "rect") out.push(...pptxRect(el2, ctx));
2755
+ else if (tag === "circle") out.push(...pptxCircle(el2, ctx));
2756
+ else if (tag === "polygon") out.push(...pptxPolygon(el2, ctx));
2757
+ else if (tag === "path") out.push(...pptxPath(el2, ctx));
2758
+ else if (tag === "line") out.push(...pptxLine(el2, ctx));
2759
+ else if (tag === "polyline") out.push(...pptxPolyline(el2, ctx));
2760
+ else if (tag === "text") out.push(...pptxText(el2, ctx));
2761
+ }
2762
+ return out.filter(Boolean).join("\n");
2763
+ }
2764
+ function pptxRect(el2, ctx) {
2765
+ const x = numAttr(el2, "x");
2766
+ const y = numAttr(el2, "y");
2767
+ const w = numAttr(el2, "width");
2768
+ const h = numAttr(el2, "height");
2769
+ if (w <= 0 || h <= 0) return [];
2770
+ const p = mapBox(ctx.tx, x, y, w, h);
2771
+ const rx = Math.max(numAttr(el2, "rx"), numAttr(el2, "ry"));
2772
+ return [shapeXml(
2773
+ ctx.nextId++,
2774
+ "rect",
2775
+ p.x,
2776
+ p.y,
2777
+ p.w,
2778
+ p.h,
2779
+ rx > 0 ? "roundRect" : "rect",
2780
+ fillXml(colorAttr(el2, "fill", "none")),
2781
+ lineXml(lineStyleFromElement(el2, ctx.tx))
2782
+ )];
2783
+ }
2784
+ function pptxCircle(el2, ctx) {
2785
+ const cx = numAttr(el2, "cx");
2786
+ const cy = numAttr(el2, "cy");
2787
+ const r = numAttr(el2, "r");
2788
+ if (r <= 0) return [];
2789
+ const p = mapBox(ctx.tx, cx - r, cy - r, r * 2, r * 2);
2790
+ return [shapeXml(
2791
+ ctx.nextId++,
2792
+ "circle",
2793
+ p.x,
2794
+ p.y,
2795
+ p.w,
2796
+ p.h,
2797
+ "ellipse",
2798
+ fillXml(colorAttr(el2, "fill", "#000")),
2799
+ lineXml(lineStyleFromElement(el2, ctx.tx))
2800
+ )];
2801
+ }
2802
+ function pptxPolygon(el2, ctx) {
2803
+ const points = parsePoints(el2.getAttribute("points"));
2804
+ if (points.length < 3) return [];
2805
+ return [freeformXml(
2806
+ ctx.nextId++,
2807
+ "polygon",
2808
+ points.map((point) => mapPoint(ctx.tx, point[0], point[1])),
2809
+ fillXml(colorAttr(el2, "fill", "none")),
2810
+ lineXml(lineStyleFromElement(el2, ctx.tx))
2811
+ )];
2812
+ }
2813
+ function pptxPath(el2, ctx) {
2814
+ const points = parsePathPoints(el2.getAttribute("d"));
2815
+ if (points.length < 2) return [];
2816
+ const line = lineStyleFromElement(el2, ctx.tx);
2817
+ const out = [];
2818
+ for (let i = 0; i < points.length - 1; i++) {
2819
+ const a = mapPoint(ctx.tx, points[i][0], points[i][1]);
2820
+ const b = mapPoint(ctx.tx, points[i + 1][0], points[i + 1][1]);
2821
+ out.push(connectorXml(ctx.nextId++, "line", a.x, a.y, b.x, b.y, line));
2822
+ }
2823
+ return out;
2824
+ }
2825
+ function pptxLine(el2, ctx) {
2826
+ const a = mapPoint(ctx.tx, numAttr(el2, "x1"), numAttr(el2, "y1"));
2827
+ const b = mapPoint(ctx.tx, numAttr(el2, "x2"), numAttr(el2, "y2"));
2828
+ return [connectorXml(ctx.nextId++, "line", a.x, a.y, b.x, b.y, lineStyleFromElement(el2, ctx.tx))];
2829
+ }
2830
+ function pptxPolyline(el2, ctx) {
2831
+ const points = parsePoints(el2.getAttribute("points"));
2832
+ if (points.length < 2) return [];
2833
+ const line = lineStyleFromElement(el2, ctx.tx);
2834
+ const out = [];
2835
+ for (let i = 0; i < points.length - 1; i++) {
2836
+ const a = mapPoint(ctx.tx, points[i][0], points[i][1]);
2837
+ const b = mapPoint(ctx.tx, points[i + 1][0], points[i + 1][1]);
2838
+ out.push(connectorXml(ctx.nextId++, "line", a.x, a.y, b.x, b.y, line));
2839
+ }
2840
+ return out;
2841
+ }
2842
+ function pptxText(el2, ctx) {
2843
+ const text = (el2.textContent ?? "").trim();
2844
+ if (!text) return [];
2845
+ const x = numAttr(el2, "x");
2846
+ const y = numAttr(el2, "y");
2847
+ const fontSize = numAttr(el2, "font-size", 2.8);
2848
+ const widthSvg = estimateSvgTextWidth(text, fontSize);
2849
+ const heightSvg = fontSize * 1.35;
2850
+ const anchor = el2.getAttribute("text-anchor") ?? "start";
2851
+ const baseline = el2.getAttribute("dominant-baseline") ?? "alphabetic";
2852
+ let left = x;
2853
+ if (anchor === "middle") left -= widthSvg / 2;
2854
+ else if (anchor === "end") left -= widthSvg;
2855
+ let top = y - fontSize * 0.95;
2856
+ if (baseline === "middle" || baseline === "central") top = y - heightSvg / 2;
2857
+ const p = mapBox(ctx.tx, left, top, widthSvg, heightSvg);
2858
+ const fontPt = Math.max(1, Math.round(fontSize * ctx.tx.scaleY / 12700 * 100) / 100);
2859
+ const align = anchor === "middle" ? "ctr" : anchor === "end" ? "r" : "l";
2860
+ return [textBoxXml(ctx.nextId++, p.x, p.y, p.w, p.h, text, fontPt, align, colorAttr(el2, "fill", "#000"))];
2861
+ }
2862
+ function mapPoint(tx, x, y) {
2863
+ return {
2864
+ x: Math.round(tx.fitX + (x - tx.minX) * tx.scaleX),
2865
+ y: Math.round(tx.fitY + (y - tx.minY) * tx.scaleY)
2866
+ };
2867
+ }
2868
+ function mapBox(tx, x, y, w, h) {
2869
+ const a = mapPoint(tx, x, y);
2870
+ return { x: a.x, y: a.y, w: Math.max(1, Math.round(w * tx.scaleX)), h: Math.max(1, Math.round(h * tx.scaleY)) };
2871
+ }
2872
+ function numAttr(el2, name, fallback = 0) {
2873
+ const n = Number.parseFloat(el2.getAttribute(name) ?? "");
2874
+ return Number.isFinite(n) ? n : fallback;
2875
+ }
2876
+ function colorAttr(el2, name, fallback) {
2877
+ const value = (el2.getAttribute(name) ?? fallback).trim();
2878
+ if (!value || value === "none" || value === "transparent") return "none";
2879
+ if (value.startsWith("#")) {
2880
+ const hex = value.slice(1);
2881
+ if (/^[0-9a-f]{3}$/i.test(hex)) return hex.split("").map((c) => c + c).join("").toUpperCase();
2882
+ if (/^[0-9a-f]{6}$/i.test(hex)) return hex.toUpperCase();
2883
+ }
2884
+ if (value.toLowerCase() === "white") return "FFFFFF";
2885
+ if (value.toLowerCase() === "black") return "000000";
2886
+ return fallback === "none" ? "none" : "000000";
2887
+ }
2888
+ function lineStyleFromElement(el2, tx) {
2889
+ const stroke = colorAttr(el2, "stroke", "none");
2890
+ if (stroke === "none") return null;
2891
+ const svgWidth = numAttr(el2, "stroke-width", 0.4);
2892
+ const width = Math.max(1270, Math.round(svgWidth * (tx.scaleX + tx.scaleY) / 2));
2893
+ return { color: stroke, width, dash: !!el2.getAttribute("stroke-dasharray") };
2894
+ }
2895
+ function parsePoints(raw) {
2896
+ if (!raw) return [];
2897
+ const nums = raw.trim().split(/[\s,]+/).map(Number).filter(Number.isFinite);
2898
+ const out = [];
2899
+ for (let i = 0; i + 1 < nums.length; i += 2) out.push([nums[i], nums[i + 1]]);
2900
+ return out;
2901
+ }
2902
+ function parsePathPoints(raw) {
2903
+ if (!raw) return [];
2904
+ const nums = raw.replace(/[MLZ]/gi, " ").trim().split(/[\s,]+/).map(Number).filter(Number.isFinite);
2905
+ const out = [];
2906
+ for (let i = 0; i + 1 < nums.length; i += 2) out.push([nums[i], nums[i + 1]]);
2907
+ return out;
2908
+ }
2909
+ function estimateSvgTextWidth(text, fontSize) {
2910
+ let width = 0;
2911
+ for (const char of text) {
2912
+ if (char === " ") width += fontSize * 0.35;
2913
+ else if (char.charCodeAt(0) <= 127) width += fontSize * 0.58;
2914
+ else width += fontSize;
2915
+ }
2916
+ return Math.max(fontSize * 2.2, width + fontSize * 0.8);
2917
+ }
2918
+ function shapeXml(id, name, x, y, w, h, preset, fill, line) {
2919
+ return `<p:sp>
2920
+ <p:nvSpPr><p:cNvPr id="${id}" name="${xmlAttr(name)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
2921
+ <p:spPr><a:xfrm><a:off x="${x}" y="${y}"/><a:ext cx="${w}" cy="${h}"/></a:xfrm><a:prstGeom prst="${preset}"><a:avLst/></a:prstGeom>${fill}${line}</p:spPr>
2922
+ </p:sp>`;
2923
+ }
2924
+ function connectorXml(id, name, x1, y1, x2, y2, line) {
2925
+ const x = Math.min(x1, x2);
2926
+ const y = Math.min(y1, y2);
2927
+ const w = Math.max(1, Math.abs(x2 - x1));
2928
+ const h = Math.max(1, Math.abs(y2 - y1));
2929
+ const flipH = x2 < x1 ? ' flipH="1"' : "";
2930
+ const flipV = y2 < y1 ? ' flipV="1"' : "";
2931
+ return `<p:cxnSp>
2932
+ <p:nvCxnSpPr><p:cNvPr id="${id}" name="${xmlAttr(name)}"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr>
2933
+ <p:spPr><a:xfrm${flipH}${flipV}><a:off x="${x}" y="${y}"/><a:ext cx="${w}" cy="${h}"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom>${lineXml(line)}</p:spPr>
2934
+ </p:cxnSp>`;
2935
+ }
2936
+ function freeformXml(id, name, points, fill, line) {
2937
+ const left = Math.min(...points.map((p) => p.x));
2938
+ const top = Math.min(...points.map((p) => p.y));
2939
+ const right = Math.max(...points.map((p) => p.x));
2940
+ const bottom = Math.max(...points.map((p) => p.y));
2941
+ const w = Math.max(1, right - left);
2942
+ const h = Math.max(1, bottom - top);
2943
+ const local = points.map((p) => ({ x: p.x - left, y: p.y - top }));
2944
+ const first = local[0];
2945
+ const rest = local.slice(1).map((p) => `<a:lnTo><a:pt x="${p.x}" y="${p.y}"/></a:lnTo>`).join("");
2946
+ return `<p:sp>
2947
+ <p:nvSpPr><p:cNvPr id="${id}" name="${xmlAttr(name)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
2948
+ <p:spPr><a:xfrm><a:off x="${left}" y="${top}"/><a:ext cx="${w}" cy="${h}"/></a:xfrm><a:custGeom><a:avLst/><a:gdLst/><a:ahLst/><a:cxnLst/><a:rect l="l" t="t" r="r" b="b"/><a:pathLst><a:path w="${w}" h="${h}"><a:moveTo><a:pt x="${first.x}" y="${first.y}"/></a:moveTo>${rest}<a:close/></a:path></a:pathLst></a:custGeom>${fill}${line}</p:spPr>
2949
+ </p:sp>`;
2950
+ }
2951
+ function textBoxXml(id, x, y, w, h, text, fontPt, align, color) {
2952
+ const sz = Math.max(100, Math.round(fontPt * 100));
2953
+ return `<p:sp>
2954
+ <p:nvSpPr><p:cNvPr id="${id}" name="text"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
2955
+ <p:spPr><a:xfrm><a:off x="${x}" y="${y}"/><a:ext cx="${w}" cy="${h}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/><a:ln><a:noFill/></a:ln></p:spPr>
2956
+ <p:txBody><a:bodyPr wrap="none" anchor="ctr" lIns="0" tIns="0" rIns="0" bIns="0"><a:spAutoFit/></a:bodyPr><a:lstStyle/><a:p><a:pPr algn="${align}"/><a:r><a:rPr lang="ja-JP" sz="${sz}" dirty="0">${fillXml(color)}</a:rPr><a:t>${xmlText(text)}</a:t></a:r></a:p></p:txBody>
2957
+ </p:sp>`;
2958
+ }
2959
+ function fillXml(color) {
2960
+ return color === "none" ? "<a:noFill/>" : `<a:solidFill><a:srgbClr val="${xmlAttr(color)}"/></a:solidFill>`;
2961
+ }
2962
+ function lineXml(line) {
2963
+ if (!line) return "<a:ln><a:noFill/></a:ln>";
2964
+ const dash = line.dash ? '<a:prstDash val="dash"/>' : '<a:prstDash val="solid"/>';
2965
+ return `<a:ln w="${line.width}" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:srgbClr val="${xmlAttr(line.color)}"/></a:solidFill>${dash}</a:ln>`;
2966
+ }
2967
+ function xmlText(text) {
2968
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2969
+ }
2970
+ function xmlAttr(text) {
2971
+ return xmlText(text).replace(/"/g, "&quot;");
2972
+ }
2973
+ function createStoredZip(entries) {
2974
+ const encoder = new TextEncoder();
2975
+ const files = entries.map(([name, data]) => {
2976
+ const bytes = typeof data === "string" ? encoder.encode(data) : data;
2977
+ return { name, nameBytes: encoder.encode(name), bytes, crc: crc32(bytes) };
2978
+ });
2979
+ const chunks = [];
2980
+ const central = [];
2981
+ let offset = 0;
2982
+ for (const file of files) {
2983
+ const local = zipLocalHeader(file.nameBytes, file.bytes, file.crc);
2984
+ chunks.push(local, file.nameBytes, file.bytes);
2985
+ central.push(zipCentralHeader(file.nameBytes, file.bytes, file.crc, offset), file.nameBytes);
2986
+ offset += local.length + file.nameBytes.length + file.bytes.length;
2987
+ }
2988
+ const centralOffset = offset;
2989
+ const centralSize = central.reduce((sum, chunk) => sum + chunk.length, 0);
2990
+ return concatBytes([...chunks, ...central, zipEndRecord(files.length, centralSize, centralOffset)]);
2991
+ }
2992
+ function zipLocalHeader(name, data, crc) {
2993
+ const out = new Uint8Array(30);
2994
+ const view = new DataView(out.buffer);
2995
+ view.setUint32(0, 67324752, true);
2996
+ view.setUint16(4, 20, true);
2997
+ view.setUint16(6, 0, true);
2998
+ view.setUint16(8, 0, true);
2999
+ view.setUint16(10, 0, true);
3000
+ view.setUint16(12, 0, true);
3001
+ view.setUint32(14, crc, true);
3002
+ view.setUint32(18, data.length, true);
3003
+ view.setUint32(22, data.length, true);
3004
+ view.setUint16(26, name.length, true);
3005
+ view.setUint16(28, 0, true);
3006
+ return out;
3007
+ }
3008
+ function zipCentralHeader(name, data, crc, offset) {
3009
+ const out = new Uint8Array(46);
3010
+ const view = new DataView(out.buffer);
3011
+ view.setUint32(0, 33639248, true);
3012
+ view.setUint16(4, 20, true);
3013
+ view.setUint16(6, 20, true);
3014
+ view.setUint16(8, 0, true);
3015
+ view.setUint16(10, 0, true);
3016
+ view.setUint16(12, 0, true);
3017
+ view.setUint16(14, 0, true);
3018
+ view.setUint32(16, crc, true);
3019
+ view.setUint32(20, data.length, true);
3020
+ view.setUint32(24, data.length, true);
3021
+ view.setUint16(28, name.length, true);
3022
+ view.setUint16(30, 0, true);
3023
+ view.setUint16(32, 0, true);
3024
+ view.setUint16(34, 0, true);
3025
+ view.setUint16(36, 0, true);
3026
+ view.setUint32(38, 0, true);
3027
+ view.setUint32(42, offset, true);
3028
+ return out;
3029
+ }
3030
+ function zipEndRecord(entries, centralSize, centralOffset) {
3031
+ const out = new Uint8Array(22);
3032
+ const view = new DataView(out.buffer);
3033
+ view.setUint32(0, 101010256, true);
3034
+ view.setUint16(4, 0, true);
3035
+ view.setUint16(6, 0, true);
3036
+ view.setUint16(8, entries, true);
3037
+ view.setUint16(10, entries, true);
3038
+ view.setUint32(12, centralSize, true);
3039
+ view.setUint32(16, centralOffset, true);
3040
+ view.setUint16(20, 0, true);
3041
+ return out;
3042
+ }
3043
+ function concatBytes(chunks) {
3044
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
3045
+ const out = new Uint8Array(total);
3046
+ let offset = 0;
3047
+ for (const chunk of chunks) {
3048
+ out.set(chunk, offset);
3049
+ offset += chunk.length;
3050
+ }
3051
+ return out;
3052
+ }
3053
+ function crc32(bytes) {
3054
+ let crc = 4294967295;
3055
+ for (const byte of bytes) {
3056
+ crc ^= byte;
3057
+ for (let i = 0; i < 8; i++) {
3058
+ crc = crc >>> 1 ^ 3988292384 & -(crc & 1);
3059
+ }
3060
+ }
3061
+ return (crc ^ 4294967295) >>> 0;
3062
+ }
3063
+ function contentTypesXml() {
3064
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3065
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
3066
+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
3067
+ <Default Extension="xml" ContentType="application/xml"/>
3068
+ <Default Extension="png" ContentType="image/png"/>
3069
+ <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
3070
+ <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
3071
+ <Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>
3072
+ <Override PartName="/ppt/slides/slide1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
3073
+ <Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>
3074
+ <Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>
3075
+ <Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>
3076
+ </Types>`;
3077
+ }
3078
+ function contentTypesXmlEditable() {
3079
+ return contentTypesXml().replace('<Default Extension="png" ContentType="image/png"/>\n', "");
3080
+ }
3081
+ function rootRelsXml() {
3082
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3083
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3084
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>
3085
+ <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
3086
+ <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
3087
+ </Relationships>`;
3088
+ }
3089
+ function appPropsXml() {
3090
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3091
+ <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
3092
+ <Application>pdgkit</Application>
3093
+ <PresentationFormat>Widescreen</PresentationFormat>
3094
+ <Slides>1</Slides>
3095
+ <Notes>0</Notes>
3096
+ <HiddenSlides>0</HiddenSlides>
3097
+ </Properties>`;
3098
+ }
3099
+ function corePropsXml(iso) {
3100
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3101
+ <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
3102
+ <dc:title>pdgkit figure</dc:title>
3103
+ <dc:creator>pdgkit</dc:creator>
3104
+ <cp:lastModifiedBy>pdgkit</cp:lastModifiedBy>
3105
+ <dcterms:created xsi:type="dcterms:W3CDTF">${iso}</dcterms:created>
3106
+ <dcterms:modified xsi:type="dcterms:W3CDTF">${iso}</dcterms:modified>
3107
+ </cp:coreProperties>`;
3108
+ }
3109
+ function presentationXml() {
3110
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3111
+ <p:presentation xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
3112
+ <p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>
3113
+ <p:sldIdLst><p:sldId id="256" r:id="rId2"/></p:sldIdLst>
3114
+ <p:sldSz cx="${PPTX_SLIDE_W}" cy="${PPTX_SLIDE_H}" type="screen16x9"/>
3115
+ <p:notesSz cx="6858000" cy="9144000"/>
3116
+ <p:defaultTextStyle/>
3117
+ </p:presentation>`;
3118
+ }
3119
+ function presentationRelsXml() {
3120
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3121
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3122
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="slideMasters/slideMaster1.xml"/>
3123
+ <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml"/>
3124
+ </Relationships>`;
3125
+ }
3126
+ function slideXml(fit) {
3127
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3128
+ <p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
3129
+ <p:cSld name="pdgkit">
3130
+ <p:bg><p:bgPr><a:solidFill><a:srgbClr val="FFFFFF"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>
3131
+ <p:spTree>
3132
+ <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
3133
+ <p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${PPTX_SLIDE_W}" cy="${PPTX_SLIDE_H}"/><a:chOff x="0" y="0"/><a:chExt cx="${PPTX_SLIDE_W}" cy="${PPTX_SLIDE_H}"/></a:xfrm></p:grpSpPr>
3134
+ <p:pic>
3135
+ <p:nvPicPr><p:cNvPr id="2" name="figure.png"/><p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr>
3136
+ <p:blipFill><a:blip r:embed="rId1"/><a:stretch><a:fillRect/></a:stretch></p:blipFill>
3137
+ <p:spPr><a:xfrm><a:off x="${Math.round(fit.x)}" y="${Math.round(fit.y)}"/><a:ext cx="${Math.round(fit.width)}" cy="${Math.round(fit.height)}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr>
3138
+ </p:pic>
3139
+ </p:spTree>
3140
+ </p:cSld>
3141
+ <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr>
3142
+ </p:sld>`;
3143
+ }
3144
+ function slideRelsXml() {
3145
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3146
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3147
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image1.png"/>
3148
+ <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
3149
+ </Relationships>`;
3150
+ }
3151
+ function editableSlideXml(shapes) {
3152
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3153
+ <p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
3154
+ <p:cSld name="pdgkit">
3155
+ <p:bg><p:bgPr><a:solidFill><a:srgbClr val="FFFFFF"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>
3156
+ <p:spTree>
3157
+ <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
3158
+ <p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${PPTX_SLIDE_W}" cy="${PPTX_SLIDE_H}"/><a:chOff x="0" y="0"/><a:chExt cx="${PPTX_SLIDE_W}" cy="${PPTX_SLIDE_H}"/></a:xfrm></p:grpSpPr>
3159
+ ${shapes}
3160
+ </p:spTree>
3161
+ </p:cSld>
3162
+ <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr>
3163
+ </p:sld>`;
3164
+ }
3165
+ function editableSlideRelsXml() {
3166
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3167
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3168
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
3169
+ </Relationships>`;
3170
+ }
3171
+ function slideMasterXml() {
3172
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3173
+ <p:sldMaster xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
3174
+ <p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${PPTX_SLIDE_W}" cy="${PPTX_SLIDE_H}"/><a:chOff x="0" y="0"/><a:chExt cx="${PPTX_SLIDE_W}" cy="${PPTX_SLIDE_H}"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>
3175
+ <p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>
3176
+ <p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst>
3177
+ <p:txStyles><p:titleStyle/><p:bodyStyle/><p:otherStyle/></p:txStyles>
3178
+ </p:sldMaster>`;
3179
+ }
3180
+ function slideMasterRelsXml() {
3181
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3182
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3183
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
3184
+ <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="../theme/theme1.xml"/>
3185
+ </Relationships>`;
3186
+ }
3187
+ function slideLayoutXml() {
3188
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3189
+ <p:sldLayout xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" type="blank" preserve="1">
3190
+ <p:cSld name="Blank"><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${PPTX_SLIDE_W}" cy="${PPTX_SLIDE_H}"/><a:chOff x="0" y="0"/><a:chExt cx="${PPTX_SLIDE_W}" cy="${PPTX_SLIDE_H}"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>
3191
+ <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr>
3192
+ </p:sldLayout>`;
3193
+ }
3194
+ function slideLayoutRelsXml() {
3195
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3196
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3197
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="../slideMasters/slideMaster1.xml"/>
3198
+ </Relationships>`;
3199
+ }
3200
+ function themeXml() {
3201
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3202
+ <a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="pdgkit">
3203
+ <a:themeElements>
3204
+ <a:clrScheme name="pdgkit"><a:dk1><a:srgbClr val="000000"/></a:dk1><a:lt1><a:srgbClr val="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="000000"/></a:dk2><a:lt2><a:srgbClr val="FFFFFF"/></a:lt2><a:accent1><a:srgbClr val="0B5FFF"/></a:accent1><a:accent2><a:srgbClr val="666666"/></a:accent2><a:accent3><a:srgbClr val="999999"/></a:accent3><a:accent4><a:srgbClr val="CCCCCC"/></a:accent4><a:accent5><a:srgbClr val="333333"/></a:accent5><a:accent6><a:srgbClr val="111111"/></a:accent6><a:hlink><a:srgbClr val="0B5FFF"/></a:hlink><a:folHlink><a:srgbClr val="0B5FFF"/></a:folHlink></a:clrScheme>
3205
+ <a:fontScheme name="pdgkit"><a:majorFont><a:latin typeface="Arial"/><a:ea typeface="Yu Gothic"/><a:cs typeface="Arial"/></a:majorFont><a:minorFont><a:latin typeface="Arial"/><a:ea typeface="Yu Gothic"/><a:cs typeface="Arial"/></a:minorFont></a:fontScheme>
3206
+ <a:fmtScheme name="pdgkit"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:fillStyleLst><a:lnStyleLst><a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:bgFillStyleLst></a:fmtScheme>
3207
+ </a:themeElements>
3208
+ <a:objectDefaults/>
3209
+ <a:extraClrSchemeLst/>
3210
+ </a:theme>`;
3211
+ }
3212
+
3213
+ // src/node/pdf.ts
3214
+ async function renderToPdf(source, opts = {}) {
3215
+ const { lang = "ja", bleed = 3, vector = true, scale = 8 } = opts;
3216
+ const { svg, viewBox } = renderToSvg(source, { lang, crop: true, bleed });
3217
+ if (vector) {
3218
+ try {
3219
+ return await renderVectorPdf(svg, viewBox);
3220
+ } catch {
3221
+ }
3222
+ }
3223
+ return await renderRasterPdf(source, viewBox, { lang, scale, bleed });
3224
+ }
3225
+ async function renderVectorPdf(svg, viewBox) {
3226
+ const { JSDOM } = await import("jsdom");
3227
+ const { jsPDF } = await import("jspdf");
3228
+ const { svg2pdf } = await import("svg2pdf.js");
3229
+ const dom = new JSDOM(svg, { contentType: "image/svg+xml" });
3230
+ patchSvgGeometry(dom.window);
3231
+ const svgEl = dom.window.document.documentElement;
3232
+ svgEl.setAttribute("font-family", FONT_FAMILY_NAME);
3233
+ for (const t of Array.from(dom.window.document.getElementsByTagName("text"))) {
3234
+ t.setAttribute("font-family", FONT_FAMILY_NAME);
3235
+ }
3236
+ const orientation = viewBox.width > viewBox.height ? "l" : "p";
3237
+ const pdf = new jsPDF({ unit: "mm", format: "a4", orientation });
3238
+ try {
3239
+ pdf.addFileToVFS(`${FONT_FAMILY_NAME}.ttf`, loadFontBase64());
3240
+ pdf.addFont(`${FONT_FAMILY_NAME}.ttf`, FONT_FAMILY_NAME, "normal");
3241
+ } catch {
3242
+ }
3243
+ const pageW = pdf.internal.pageSize.getWidth();
3244
+ const pageH = pdf.internal.pageSize.getHeight();
3245
+ const fit = fitRectIntoPage(viewBox.width, viewBox.height, pageW, pageH, 10);
3246
+ await svg2pdf(svgEl, pdf, {
3247
+ x: fit.x,
3248
+ y: fit.y,
3249
+ width: fit.width,
3250
+ height: fit.height
3251
+ });
3252
+ return new Uint8Array(pdf.output("arraybuffer"));
3253
+ }
3254
+ async function renderRasterPdf(source, viewBox, opts) {
3255
+ const { jsPDF } = await import("jspdf");
3256
+ const png = await renderToPng(source, opts);
3257
+ const orientation = viewBox.width > viewBox.height ? "l" : "p";
3258
+ const pdf = new jsPDF({ unit: "mm", format: "a4", orientation });
3259
+ const pageW = pdf.internal.pageSize.getWidth();
3260
+ const pageH = pdf.internal.pageSize.getHeight();
3261
+ const fit = fitRectIntoPage(viewBox.width, viewBox.height, pageW, pageH, 10);
3262
+ const dataUrl = "data:image/png;base64," + Buffer.from(png).toString("base64");
3263
+ pdf.addImage(dataUrl, "PNG", fit.x, fit.y, fit.width, fit.height);
3264
+ return new Uint8Array(pdf.output("arraybuffer"));
3265
+ }
3266
+ function patchSvgGeometry(win) {
3267
+ const proto = win.SVGElement?.prototype;
3268
+ if (!proto) return;
3269
+ const numOf = (el2, name, d = 0) => {
3270
+ const v = Number.parseFloat(el2.getAttribute?.(name) ?? "");
3271
+ return Number.isFinite(v) ? v : d;
3272
+ };
3273
+ proto.getBBox = function() {
3274
+ const tag = (this.tagName ?? "").toLowerCase();
3275
+ if (tag === "rect") {
3276
+ return { x: numOf(this, "x"), y: numOf(this, "y"), width: numOf(this, "width"), height: numOf(this, "height") };
3277
+ }
3278
+ if (tag === "circle") {
3279
+ const r = numOf(this, "r");
3280
+ return { x: numOf(this, "cx") - r, y: numOf(this, "cy") - r, width: 2 * r, height: 2 * r };
3281
+ }
3282
+ if (tag === "text") {
3283
+ const fs2 = numOf(this, "font-size", 2.8);
3284
+ return { x: numOf(this, "x"), y: numOf(this, "y") - fs2, width: estimateTextWidth(this.textContent ?? "", fs2), height: fs2 * 1.2 };
3285
+ }
3286
+ return { x: 0, y: 0, width: 0, height: 0 };
3287
+ };
3288
+ proto.getComputedTextLength = function() {
3289
+ const fs2 = numOf(this, "font-size", 2.8);
3290
+ return estimateTextWidth(this.textContent ?? "", fs2);
3291
+ };
3292
+ for (const m of ["getCTM", "getScreenCTM"]) {
3293
+ if (typeof proto[m] !== "function") {
3294
+ proto[m] = function() {
3295
+ return null;
3296
+ };
3297
+ }
3298
+ }
3299
+ }
3300
+
3301
+ // src/node/pptx.ts
3302
+ async function renderToPptx(source, opts = {}) {
3303
+ const { lang = "ja", editable = false, scale = 8, bleed = 3 } = opts;
3304
+ if (editable) {
3305
+ const model = buildSvgModel(source, { lang, crop: true, bleed });
3306
+ return buildEditablePptxPackage(model.el);
3307
+ }
3308
+ const { viewBox } = buildSvgModel(source, { lang, crop: true, bleed });
3309
+ const dims = rasterDimensions(viewBox, scale);
3310
+ const png = await renderToPng(source, { lang, scale, bleed });
3311
+ return buildPptxPackage(png, dims.width, dims.height);
3312
+ }
3313
+
3314
+ // src/node/index.ts
3315
+ var VERSION = "0.1.0";
3316
+ function loadAuthoringGuide() {
3317
+ return readFileSync2(resolvePackageFile("docs", "ai-authoring-guide.md"), "utf8");
3318
+ }
3319
+
3320
+ // bin/pdgkit.ts
3321
+ function parseArgs(args) {
3322
+ const positionals = [];
3323
+ const opts = /* @__PURE__ */ new Map();
3324
+ for (let i = 0; i < args.length; i++) {
3325
+ const a = args[i];
3326
+ if (a === "-o" || a === "--out") {
3327
+ opts.set("out", args[++i] ?? "");
3328
+ } else if (a.startsWith("--no-")) {
3329
+ opts.set(a.slice(5), "false");
3330
+ } else if (a.startsWith("--")) {
3331
+ const eq = a.indexOf("=");
3332
+ if (eq !== -1) {
3333
+ opts.set(a.slice(2, eq), a.slice(eq + 1));
3334
+ } else {
3335
+ const next = args[i + 1];
3336
+ if (next !== void 0 && !next.startsWith("-")) {
3337
+ opts.set(a.slice(2), next);
3338
+ i++;
3339
+ } else {
3340
+ opts.set(a.slice(2), true);
3341
+ }
3342
+ }
3343
+ } else {
3344
+ positionals.push(a);
3345
+ }
3346
+ }
3347
+ return { positionals, opts };
3348
+ }
3349
+ function fail(message, code = 1) {
3350
+ process.stderr.write(`pdgkit: ${message}
3351
+ `);
3352
+ process.exit(code);
3353
+ }
3354
+ function readStdin() {
3355
+ try {
3356
+ return fs.readFileSync(0, "utf8");
3357
+ } catch {
3358
+ return "";
3359
+ }
3360
+ }
3361
+ function resolveSource(positional, opts) {
3362
+ const sample = opts.get("sample");
3363
+ if (typeof sample === "string") {
3364
+ if (!SAMPLE_ORDER.includes(sample)) {
3365
+ fail(`unknown sample "${sample}". Run "pdgkit samples" to list them.`, 2);
3366
+ }
3367
+ return SAMPLES[sample].source;
3368
+ }
3369
+ if (positional && positional !== "-") {
3370
+ try {
3371
+ return fs.readFileSync(positional, "utf8");
3372
+ } catch {
3373
+ fail(`cannot read file: ${positional}`, 2);
3374
+ }
3375
+ }
3376
+ return readStdin();
3377
+ }
3378
+ function getLang(opts) {
3379
+ const lang = opts.get("lang");
3380
+ if (lang === "ja" || lang === "en" || lang === "both") return lang;
3381
+ if (lang === void 0 || lang === true) return "ja";
3382
+ fail(`invalid --lang "${String(lang)}" (expected ja, en, or both)`, 2);
3383
+ }
3384
+ function writeOut(opts, text) {
3385
+ const out = opts.get("out");
3386
+ if (typeof out === "string" && out) {
3387
+ fs.writeFileSync(out, text, "utf8");
3388
+ process.stderr.write(`pdgkit: wrote ${out}
3389
+ `);
3390
+ } else {
3391
+ process.stdout.write(text.endsWith("\n") ? text : text + "\n");
3392
+ }
3393
+ }
3394
+ function writeBinary(opts, bytes) {
3395
+ const out = opts.get("out");
3396
+ if (typeof out !== "string" || !out) {
3397
+ fail("binary output requires -o <file>", 2);
3398
+ }
3399
+ fs.writeFileSync(out, bytes);
3400
+ process.stderr.write(`pdgkit: wrote ${out} (${bytes.length} bytes)
3401
+ `);
3402
+ }
3403
+ function formatDiagnostic(d) {
3404
+ const tag = d.severity === "error" ? "ERROR" : d.severity === "warning" ? "WARN " : "INFO ";
3405
+ return ` ${tag} line ${d.line}:${d.col} ${d.message}`;
3406
+ }
3407
+ async function cmdRender(args) {
3408
+ const source = resolveSource(args.positionals[0], args.opts);
3409
+ const lang = getLang(args.opts);
3410
+ const to = args.opts.get("to") ?? "svg";
3411
+ const crop = args.opts.get("crop") !== "false";
3412
+ switch (to) {
3413
+ case "svg": {
3414
+ const { svg } = renderToSvg(source, { lang, crop });
3415
+ writeOut(args.opts, svg);
3416
+ return;
3417
+ }
3418
+ case "png":
3419
+ writeBinary(args.opts, await renderToPng(source, { lang }));
3420
+ return;
3421
+ case "jpeg":
3422
+ case "jpg":
3423
+ writeBinary(args.opts, await renderToJpeg(source, { lang }));
3424
+ return;
3425
+ case "pdf":
3426
+ writeBinary(args.opts, await renderToPdf(source, { lang }));
3427
+ return;
3428
+ case "pptx":
3429
+ writeBinary(args.opts, await renderToPptx(source, { lang, editable: args.opts.get("editable") === true }));
3430
+ return;
3431
+ default:
3432
+ fail(`unknown --to "${to}" (expected svg, png, jpeg, pdf, or pptx)`, 2);
3433
+ }
3434
+ }
3435
+ function cmdValidate(args) {
3436
+ const source = resolveSource(args.positionals[0], args.opts);
3437
+ const result = validate(source);
3438
+ const lines = [];
3439
+ lines.push(`kind: ${result.kind}${result.declaredKind ? ` (declared: ${result.declaredKind})` : ""}`);
3440
+ if (result.diagnostics.length === 0) {
3441
+ lines.push("no diagnostics");
3442
+ } else {
3443
+ for (const d of result.diagnostics) lines.push(formatDiagnostic(d));
3444
+ }
3445
+ lines.push(
3446
+ `summary: ${result.counts.errors} error(s), ${result.counts.warnings} warning(s), ${result.counts.infos} info`
3447
+ );
3448
+ process.stderr.write(lines.join("\n") + "\n");
3449
+ process.exit(result.ok ? 0 : 1);
3450
+ }
3451
+ function cmdRefs(args) {
3452
+ const source = resolveSource(args.positionals[0], args.opts);
3453
+ const doc = parse(source);
3454
+ const format = args.opts.get("format") ?? "md";
3455
+ if (format === "md" || format === "markdown") {
3456
+ writeOut(args.opts, refsToMarkdown(doc));
3457
+ } else if (format === "csv") {
3458
+ writeOut(args.opts, refsToCsv(doc));
3459
+ } else {
3460
+ fail(`unknown --format "${format}" (expected md or csv)`, 2);
3461
+ }
3462
+ }
3463
+ function cmdGuide() {
3464
+ process.stdout.write(loadAuthoringGuide().replace(/\n*$/, "\n"));
3465
+ }
3466
+ function cmdSamples() {
3467
+ const rows = SAMPLE_ORDER.map((id) => ` ${id.padEnd(16)} ${SAMPLES[id].label} \u2014 ${SAMPLES[id].hint}`);
3468
+ process.stdout.write(`built-in samples:
3469
+ ${rows.join("\n")}
3470
+ `);
3471
+ }
3472
+ var HELP = `pdgkit ${VERSION} \u2014 headless engine for the PatentDSL (.pdg) language
3473
+
3474
+ usage:
3475
+ pdgkit render <input> [--to svg|png|jpeg|pdf|pptx] [--lang ja|en|both] [-o file] [--no-crop]
3476
+ pdgkit validate <input> [--lang ja|en|both]
3477
+ pdgkit refs <input> [--format md|csv] [-o file]
3478
+ pdgkit guide
3479
+ pdgkit samples
3480
+ pdgkit version
3481
+
3482
+ <input> is a file path, "-" for stdin, or omitted with --sample <id>.
3483
+ "pdgkit guide" prints the .pdg authoring guide, so an AI can read it instead of
3484
+ having it pasted in.
3485
+
3486
+ examples:
3487
+ pdgkit render fig1.pdg -o fig1.svg
3488
+ pdgkit render --sample block --lang both -o block.svg
3489
+ cat fig1.pdg | pdgkit validate -
3490
+ pdgkit refs fig1.pdg --format csv -o signs.csv
3491
+ pdgkit render fig1.pdg --to pdf -o fig1.pdf
3492
+ pdgkit render fig1.pdg --to pptx --editable -o fig1.pptx
3493
+
3494
+ formats: svg, png, jpeg, pdf, pptx (add --editable for editable PowerPoint shapes).
3495
+ an MCP server is also available: run \`pdgkit-mcp\`.`;
3496
+ async function main() {
3497
+ const argv = process.argv.slice(2);
3498
+ const cmd = argv[0];
3499
+ const rest = parseArgs(argv.slice(1));
3500
+ switch (cmd) {
3501
+ case "render":
3502
+ await cmdRender(rest);
3503
+ break;
3504
+ case "validate":
3505
+ cmdValidate(rest);
3506
+ break;
3507
+ case "refs":
3508
+ cmdRefs(rest);
3509
+ break;
3510
+ case "samples":
3511
+ cmdSamples();
3512
+ break;
3513
+ case "guide":
3514
+ cmdGuide();
3515
+ break;
3516
+ case "version":
3517
+ case "--version":
3518
+ case "-v":
3519
+ process.stdout.write(`${VERSION}
3520
+ `);
3521
+ break;
3522
+ case void 0:
3523
+ case "help":
3524
+ case "--help":
3525
+ case "-h":
3526
+ process.stdout.write(HELP + "\n");
3527
+ break;
3528
+ default:
3529
+ fail(`unknown command "${cmd}". Run "pdgkit help".`, 2);
3530
+ }
3531
+ }
3532
+ main().catch((err) => {
3533
+ fail(err instanceof Error ? err.message : String(err), 1);
3534
+ });