schematex 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +4 -3
  2. package/dist/ai/ai-sdk.cjs +10 -10
  3. package/dist/ai/ai-sdk.d.cts +2 -2
  4. package/dist/ai/ai-sdk.d.ts +2 -2
  5. package/dist/ai/ai-sdk.js +5 -5
  6. package/dist/ai/index.cjs +13 -13
  7. package/dist/ai/index.d.cts +3 -3
  8. package/dist/ai/index.d.ts +3 -3
  9. package/dist/ai/index.js +5 -5
  10. package/dist/{api-bQZ98gkJ.d.cts → api-BuFilDQB.d.cts} +1 -1
  11. package/dist/{api-bQZ98gkJ.d.ts → api-BuFilDQB.d.ts} +1 -1
  12. package/dist/browser.cjs +7 -7
  13. package/dist/browser.d.cts +2 -2
  14. package/dist/browser.d.ts +2 -2
  15. package/dist/browser.js +5 -5
  16. package/dist/{chunk-UAGSCTYI.cjs → chunk-3FKS4KQK.cjs} +22 -6
  17. package/dist/chunk-3FKS4KQK.cjs.map +1 -0
  18. package/dist/{chunk-4S2WILLW.cjs → chunk-K2SOC3XF.cjs} +5 -3
  19. package/dist/chunk-K2SOC3XF.cjs.map +1 -0
  20. package/dist/{chunk-LR4X4ZRG.js → chunk-NB56L5QK.js} +18 -14
  21. package/dist/chunk-NB56L5QK.js.map +1 -0
  22. package/dist/{chunk-VPKCW4PB.js → chunk-NNU4RGT3.js} +2820 -20
  23. package/dist/chunk-NNU4RGT3.js.map +1 -0
  24. package/dist/{chunk-E65ITQXV.cjs → chunk-NZH4GWE6.cjs} +18 -14
  25. package/dist/chunk-NZH4GWE6.cjs.map +1 -0
  26. package/dist/{chunk-DPQYGWCT.cjs → chunk-SQKLKBBK.cjs} +32 -5
  27. package/dist/chunk-SQKLKBBK.cjs.map +1 -0
  28. package/dist/{chunk-J2LVOWVY.js → chunk-TYCHEOQX.js} +22 -6
  29. package/dist/chunk-TYCHEOQX.js.map +1 -0
  30. package/dist/{chunk-MR5HU5WU.js → chunk-VLMK7MQK.js} +30 -3
  31. package/dist/chunk-VLMK7MQK.js.map +1 -0
  32. package/dist/{chunk-MSYBSOU2.cjs → chunk-XTATRNUN.cjs} +2824 -22
  33. package/dist/chunk-XTATRNUN.cjs.map +1 -0
  34. package/dist/{chunk-PGALHQFF.js → chunk-XZNPAD6E.js} +5 -3
  35. package/dist/chunk-XZNPAD6E.js.map +1 -0
  36. package/dist/diagrams/blockdiagram/index.d.cts +1 -1
  37. package/dist/diagrams/blockdiagram/index.d.ts +1 -1
  38. package/dist/diagrams/circuit/index.cjs +7 -7
  39. package/dist/diagrams/circuit/index.d.cts +1 -1
  40. package/dist/diagrams/circuit/index.d.ts +1 -1
  41. package/dist/diagrams/circuit/index.js +1 -1
  42. package/dist/diagrams/ecomap/index.cjs +6 -6
  43. package/dist/diagrams/ecomap/index.d.cts +1 -1
  44. package/dist/diagrams/ecomap/index.d.ts +1 -1
  45. package/dist/diagrams/ecomap/index.js +1 -1
  46. package/dist/diagrams/entity/index.d.cts +1 -1
  47. package/dist/diagrams/entity/index.d.ts +1 -1
  48. package/dist/diagrams/fishbone/index.d.cts +1 -1
  49. package/dist/diagrams/fishbone/index.d.ts +1 -1
  50. package/dist/diagrams/flowchart/index.cjs +7 -7
  51. package/dist/diagrams/flowchart/index.d.cts +2 -2
  52. package/dist/diagrams/flowchart/index.d.ts +2 -2
  53. package/dist/diagrams/flowchart/index.js +1 -1
  54. package/dist/diagrams/genogram/index.d.cts +1 -1
  55. package/dist/diagrams/genogram/index.d.ts +1 -1
  56. package/dist/diagrams/ladder/index.d.cts +1 -1
  57. package/dist/diagrams/ladder/index.d.ts +1 -1
  58. package/dist/diagrams/logic/index.d.cts +1 -1
  59. package/dist/diagrams/logic/index.d.ts +1 -1
  60. package/dist/diagrams/orgchart/index.d.cts +1 -1
  61. package/dist/diagrams/orgchart/index.d.ts +1 -1
  62. package/dist/diagrams/pedigree/index.d.cts +1 -1
  63. package/dist/diagrams/pedigree/index.d.ts +1 -1
  64. package/dist/diagrams/phylo/index.d.cts +1 -1
  65. package/dist/diagrams/phylo/index.d.ts +1 -1
  66. package/dist/diagrams/sld/index.d.cts +1 -1
  67. package/dist/diagrams/sld/index.d.ts +1 -1
  68. package/dist/diagrams/sociogram/index.d.cts +1 -1
  69. package/dist/diagrams/sociogram/index.d.ts +1 -1
  70. package/dist/diagrams/timing/index.d.cts +1 -1
  71. package/dist/diagrams/timing/index.d.ts +1 -1
  72. package/dist/diagrams/venn/index.d.cts +1 -1
  73. package/dist/diagrams/venn/index.d.ts +1 -1
  74. package/dist/{index-DcU88F9i.d.ts → index-CUwp4GXI.d.ts} +1 -1
  75. package/dist/{index-BTZEka65.d.cts → index-ivhNGsyU.d.cts} +1 -1
  76. package/dist/index.cjs +19 -11
  77. package/dist/index.d.cts +8 -4
  78. package/dist/index.d.ts +8 -4
  79. package/dist/index.js +4 -4
  80. package/dist/react.cjs +5 -5
  81. package/dist/react.d.cts +1 -1
  82. package/dist/react.d.ts +1 -1
  83. package/dist/react.js +4 -4
  84. package/dist/{types-C4LnMEcB.d.cts → types-Bl-Pn7Wj.d.cts} +1 -1
  85. package/dist/{types-C4LnMEcB.d.ts → types-Bl-Pn7Wj.d.ts} +1 -1
  86. package/package.json +2 -2
  87. package/dist/chunk-4S2WILLW.cjs.map +0 -1
  88. package/dist/chunk-DPQYGWCT.cjs.map +0 -1
  89. package/dist/chunk-E65ITQXV.cjs.map +0 -1
  90. package/dist/chunk-J2LVOWVY.js.map +0 -1
  91. package/dist/chunk-LR4X4ZRG.js.map +0 -1
  92. package/dist/chunk-MR5HU5WU.js.map +0 -1
  93. package/dist/chunk-MSYBSOU2.cjs.map +0 -1
  94. package/dist/chunk-PGALHQFF.js.map +0 -1
  95. package/dist/chunk-UAGSCTYI.cjs.map +0 -1
  96. package/dist/chunk-VPKCW4PB.js.map +0 -1
@@ -1,14 +1,14 @@
1
1
  import { orgchart } from './chunk-FCGHV6ZK.js';
2
- import { circuit } from './chunk-PGALHQFF.js';
2
+ import { circuit } from './chunk-XZNPAD6E.js';
3
3
  import { blockdiagram } from './chunk-UGCUNADI.js';
4
4
  import { ladder } from './chunk-U6L3FAML.js';
5
5
  import { sld } from './chunk-FE6GAUNW.js';
6
6
  import { entity } from './chunk-M5B2UUNW.js';
7
7
  import { fishbone } from './chunk-ZNDIGQJD.js';
8
8
  import { venn } from './chunk-JDTB7IKL.js';
9
- import { flowchart } from './chunk-J2LVOWVY.js';
9
+ import { flowchart, layoutFlowchart } from './chunk-TYCHEOQX.js';
10
10
  import { genogram } from './chunk-H2OEUBPO.js';
11
- import { ecomap } from './chunk-LR4X4ZRG.js';
11
+ import { ecomap } from './chunk-NB56L5QK.js';
12
12
  import { pedigree } from './chunk-OC22GGQN.js';
13
13
  import { phylo } from './chunk-YQANC7HQ.js';
14
14
  import { sociogram } from './chunk-RP5UATRA.js';
@@ -621,7 +621,7 @@ function layoutDecisionTree(ast) {
621
621
  enforceSibGap(root, sibH, sibGap);
622
622
  const levelOffsets = computeLevelOffsets(root, sibH, levelGap);
623
623
  setFinal(root, sibH, levelOffsets);
624
- const PADDING2 = 40;
624
+ const PADDING3 = 40;
625
625
  const extraLeft = ast.mode === "decision" && !sibH ? 110 : 0;
626
626
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
627
627
  for (const n of all) {
@@ -646,8 +646,8 @@ function layoutDecisionTree(ast) {
646
646
  maxX = Math.max(maxX, n.xFinal + n.size.w / 2);
647
647
  }
648
648
  }
649
- const offsetX = PADDING2 + extraLeft - minX;
650
- const offsetY = PADDING2 - minY;
649
+ const offsetX = PADDING3 + extraLeft - minX;
650
+ const offsetY = PADDING3 - minY;
651
651
  const layoutNodes = all.map((w) => ({
652
652
  node: w.node,
653
653
  x: w.xFinal + offsetX,
@@ -708,8 +708,8 @@ function layoutDecisionTree(ast) {
708
708
  for (const n of layoutNodes) if (n.node.kind === "end") endX = Math.max(endX, n.x + n.width / 2);
709
709
  payoffColumnX = endX + payoffColGap;
710
710
  }
711
- const width = Math.ceil(maxX - minX + PADDING2 * 2 + extraRight + extraLeft);
712
- const height = Math.ceil(maxY - minY + PADDING2 * 2);
711
+ const width = Math.ceil(maxX - minX + PADDING3 * 2 + extraRight + extraLeft);
712
+ const height = Math.ceil(maxY - minY + PADDING3 * 2);
713
713
  return {
714
714
  width,
715
715
  height,
@@ -2626,6 +2626,2804 @@ var timeline = {
2626
2626
  }
2627
2627
  };
2628
2628
 
2629
+ // src/diagrams/state/parser.ts
2630
+ var StateParseError = class extends Error {
2631
+ constructor(message, line2) {
2632
+ super(line2 !== void 0 ? `Line ${line2}: ${message}` : message);
2633
+ this.line = line2;
2634
+ this.name = "StateParseError";
2635
+ }
2636
+ line;
2637
+ };
2638
+ var PSEUDO_KEYWORDS = {
2639
+ initial: "initial",
2640
+ final: "final",
2641
+ choice: "choice",
2642
+ junction: "junction",
2643
+ fork: "fork",
2644
+ join: "join",
2645
+ history: "history",
2646
+ dhistory: "dhistory",
2647
+ terminate: "terminate",
2648
+ entry_point: "entry_point",
2649
+ exit_point: "exit_point"
2650
+ };
2651
+ var MERMAID_STEREOTYPE = {
2652
+ choice: "choice",
2653
+ fork: "fork",
2654
+ join: "join",
2655
+ end: "final"
2656
+ };
2657
+ function preprocess3(src) {
2658
+ const out = [];
2659
+ const lines = src.split(/\r?\n/);
2660
+ for (let i = 0; i < lines.length; i++) {
2661
+ const raw = lines[i];
2662
+ if (raw === void 0) continue;
2663
+ const trimmed = raw.trim();
2664
+ if (!trimmed) continue;
2665
+ if (trimmed.startsWith("#") || trimmed.startsWith("//") || trimmed.startsWith("%%")) continue;
2666
+ const indent = raw.length - raw.replace(/^\s+/, "").length;
2667
+ out.push({ indent, text: trimmed, line: i + 1 });
2668
+ }
2669
+ return out;
2670
+ }
2671
+ function unquote2(s) {
2672
+ if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
2673
+ return s.slice(1, -1);
2674
+ }
2675
+ return s;
2676
+ }
2677
+ function parseProps(s) {
2678
+ const out = {};
2679
+ const inner = s.replace(/^\[/, "").replace(/\]$/, "");
2680
+ if (!inner.trim()) return out;
2681
+ for (const part of inner.split(",")) {
2682
+ const idx = part.indexOf(":");
2683
+ if (idx < 0) continue;
2684
+ out[part.slice(0, idx).trim()] = unquote2(part.slice(idx + 1).trim());
2685
+ }
2686
+ return out;
2687
+ }
2688
+ function parseTransitionLabel(label) {
2689
+ const out = {};
2690
+ let rest = label.trim();
2691
+ if (!rest) return out;
2692
+ let depth = 0;
2693
+ let slashIdx = -1;
2694
+ for (let i = 0; i < rest.length; i++) {
2695
+ const c = rest[i];
2696
+ if (c === "[") depth++;
2697
+ else if (c === "]") depth--;
2698
+ else if (c === "/" && depth === 0) {
2699
+ slashIdx = i;
2700
+ break;
2701
+ }
2702
+ }
2703
+ if (slashIdx >= 0) {
2704
+ out.action = rest.slice(slashIdx + 1).trim();
2705
+ rest = rest.slice(0, slashIdx).trim();
2706
+ }
2707
+ const gMatch = rest.match(/^(?<trig>[^[]*?)\s*\[(?<guard>[^\]]*)\]\s*$/);
2708
+ if (gMatch?.groups) {
2709
+ out.guard = gMatch.groups.guard.trim();
2710
+ const trig = gMatch.groups.trig.trim();
2711
+ if (trig) out.trigger = trig;
2712
+ } else if (rest.length) {
2713
+ out.trigger = rest;
2714
+ }
2715
+ return out;
2716
+ }
2717
+ function parseActivityLine(text2) {
2718
+ const match = text2.match(/^(entry|exit|do)\s*\/\s*(.*)$/);
2719
+ if (match) {
2720
+ return { kind: match[1], action: match[2].trim() };
2721
+ }
2722
+ const parsed = parseTransitionLabel(text2);
2723
+ if (!parsed.trigger && !parsed.guard && !parsed.action) return void 0;
2724
+ return {
2725
+ kind: "internal",
2726
+ trigger: parsed.trigger,
2727
+ guard: parsed.guard,
2728
+ action: parsed.action
2729
+ };
2730
+ }
2731
+ function newPseudoId(ctx, kind) {
2732
+ ctx.pseudoCounter += 1;
2733
+ return `__${kind}_${ctx.pseudoCounter}`;
2734
+ }
2735
+ function ensureInitialAlias(ctx, parent) {
2736
+ if (!parent) {
2737
+ if (ctx.initialAlias) {
2738
+ const existing = ctx.byId.get(ctx.initialAlias);
2739
+ if (existing) return existing;
2740
+ }
2741
+ const node2 = {
2742
+ id: newPseudoId(ctx, "initial"),
2743
+ label: "",
2744
+ kind: "pseudo",
2745
+ pseudoKind: "initial",
2746
+ activities: [],
2747
+ children: []
2748
+ };
2749
+ ctx.initialAlias = node2.id;
2750
+ ctx.byId.set(node2.id, node2);
2751
+ ctx.states.push(node2);
2752
+ return node2;
2753
+ }
2754
+ for (const child of parent.children) {
2755
+ if (child.id.startsWith("__initial_") && child.label === "") return child;
2756
+ }
2757
+ const node = {
2758
+ id: newPseudoId(ctx, "initial"),
2759
+ label: "",
2760
+ kind: "pseudo",
2761
+ pseudoKind: "initial",
2762
+ activities: [],
2763
+ children: [],
2764
+ parent: parent.id
2765
+ };
2766
+ ctx.byId.set(node.id, node);
2767
+ parent.children.push(node);
2768
+ return node;
2769
+ }
2770
+ function ensureFinalAlias(ctx, parent) {
2771
+ if (!parent) {
2772
+ if (ctx.finalAlias) {
2773
+ const existing = ctx.byId.get(ctx.finalAlias);
2774
+ if (existing) return existing;
2775
+ }
2776
+ const node2 = {
2777
+ id: newPseudoId(ctx, "final"),
2778
+ label: "",
2779
+ kind: "pseudo",
2780
+ pseudoKind: "final",
2781
+ activities: [],
2782
+ children: []
2783
+ };
2784
+ ctx.finalAlias = node2.id;
2785
+ ctx.byId.set(node2.id, node2);
2786
+ ctx.states.push(node2);
2787
+ return node2;
2788
+ }
2789
+ for (const child of parent.children) {
2790
+ if (child.id.startsWith("__final_") && child.label === "") return child;
2791
+ }
2792
+ const node = {
2793
+ id: newPseudoId(ctx, "final"),
2794
+ label: "",
2795
+ kind: "pseudo",
2796
+ pseudoKind: "final",
2797
+ activities: [],
2798
+ children: [],
2799
+ parent: parent.id
2800
+ };
2801
+ ctx.byId.set(node.id, node);
2802
+ parent.children.push(node);
2803
+ return node;
2804
+ }
2805
+ function ensureSimpleState(ctx, id, parent) {
2806
+ const existing = ctx.byId.get(id);
2807
+ if (existing) return existing;
2808
+ const node = {
2809
+ id,
2810
+ label: id,
2811
+ kind: "simple",
2812
+ activities: [],
2813
+ children: [],
2814
+ parent: parent?.id
2815
+ };
2816
+ ctx.byId.set(id, node);
2817
+ if (parent) parent.children.push(node);
2818
+ else ctx.states.push(node);
2819
+ return node;
2820
+ }
2821
+ function isIdent(tok) {
2822
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(tok);
2823
+ }
2824
+ function parseStateDiagram(src) {
2825
+ const lines = preprocess3(src);
2826
+ if (lines.length === 0) {
2827
+ throw new StateParseError("Empty document");
2828
+ }
2829
+ const header = lines[0];
2830
+ const headerTok = header.text.match(/^(stateDiagram-v2|stateDiagram|state)\b/i);
2831
+ if (!headerTok) {
2832
+ throw new StateParseError(
2833
+ `Expected 'state' or 'stateDiagram' header, got '${header.text}'`,
2834
+ header.line
2835
+ );
2836
+ }
2837
+ let title2;
2838
+ let direction = "TB";
2839
+ const headerRest = header.text.slice(headerTok[0].length).trim();
2840
+ const propsMatch = headerRest.match(/\[[^\]]*\]\s*$/);
2841
+ let beforeProps = headerRest;
2842
+ if (propsMatch) {
2843
+ const props = parseProps(propsMatch[0]);
2844
+ if (props.direction === "TB" || props.direction === "LR") direction = props.direction;
2845
+ beforeProps = headerRest.slice(0, propsMatch.index).trim();
2846
+ }
2847
+ if (beforeProps.startsWith('"')) title2 = unquote2(beforeProps);
2848
+ else if (beforeProps.length > 0) title2 = beforeProps;
2849
+ const ctx = {
2850
+ states: [],
2851
+ transitions: [],
2852
+ notes: [],
2853
+ pseudoCounter: 0,
2854
+ noteCounter: 0,
2855
+ transCounter: 0,
2856
+ byId: /* @__PURE__ */ new Map()
2857
+ };
2858
+ const compositeStack = [{ parent: void 0, regionMode: false }];
2859
+ let i = 1;
2860
+ while (i < lines.length) {
2861
+ const ln = lines[i];
2862
+ const text2 = ln.text;
2863
+ const ctxTop = compositeStack[compositeStack.length - 1];
2864
+ const parent = ctxTop.parent;
2865
+ if (text2 === "}") {
2866
+ if (compositeStack.length <= 1) {
2867
+ throw new StateParseError("Unexpected '}'", ln.line);
2868
+ }
2869
+ compositeStack.pop();
2870
+ i++;
2871
+ continue;
2872
+ }
2873
+ if (text2 === "---" || text2 === "--") {
2874
+ if (!parent) {
2875
+ throw new StateParseError(
2876
+ "Region separator only allowed inside a composite",
2877
+ ln.line
2878
+ );
2879
+ }
2880
+ ctxTop.regionMode = true;
2881
+ if (!parent.regions) parent.regions = [];
2882
+ const lastIdx = parent.regions.reduce((s, r) => s + r.length, 0);
2883
+ const slice = parent.children.slice(lastIdx);
2884
+ parent.regions.push(slice);
2885
+ i++;
2886
+ continue;
2887
+ }
2888
+ const dirMatch = text2.match(/^direction\s+(TB|BT|LR|RL)\s*$/);
2889
+ if (dirMatch) {
2890
+ const d = dirMatch[1];
2891
+ direction = d === "BT" ? "TB" : d === "RL" ? "LR" : d;
2892
+ i++;
2893
+ continue;
2894
+ }
2895
+ const compMatch = text2.match(/^(?:composite|state)\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{?\s*$/);
2896
+ const isCompositeWithBrace = compMatch && text2.endsWith("{");
2897
+ if (isCompositeWithBrace) {
2898
+ const id = compMatch[1];
2899
+ const node = ensureSimpleState(ctx, id, parent);
2900
+ node.kind = "composite";
2901
+ compositeStack.push({ parent: node, regionMode: false });
2902
+ i++;
2903
+ continue;
2904
+ }
2905
+ const aliasMatch = text2.match(/^state\s+"([^"]*)"\s+as\s+([A-Za-z_][A-Za-z0-9_]*)\s*$/);
2906
+ if (aliasMatch) {
2907
+ const node = ensureSimpleState(ctx, aliasMatch[2], parent);
2908
+ node.label = aliasMatch[1];
2909
+ i++;
2910
+ continue;
2911
+ }
2912
+ const stereoMatch = text2.match(/^state\s+([A-Za-z_][A-Za-z0-9_]*)\s+<<\s*(choice|fork|join|end)\s*>>\s*$/);
2913
+ if (stereoMatch) {
2914
+ const id = stereoMatch[1];
2915
+ const kind = MERMAID_STEREOTYPE[stereoMatch[2]];
2916
+ const node = {
2917
+ id,
2918
+ label: "",
2919
+ kind: "pseudo",
2920
+ pseudoKind: kind,
2921
+ activities: [],
2922
+ children: [],
2923
+ parent: parent?.id
2924
+ };
2925
+ ctx.byId.set(id, node);
2926
+ if (parent) parent.children.push(node);
2927
+ else ctx.states.push(node);
2928
+ i++;
2929
+ continue;
2930
+ }
2931
+ const stateLabelMatch = text2.match(/^state\s+([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.+)$/);
2932
+ if (stateLabelMatch) {
2933
+ const node = ensureSimpleState(ctx, stateLabelMatch[1], parent);
2934
+ node.label = unquote2(stateLabelMatch[2].trim());
2935
+ i++;
2936
+ continue;
2937
+ }
2938
+ const pseudoMatch = text2.match(
2939
+ /^(initial|final|choice|junction|fork|join|history|dhistory|terminate|entry_point|exit_point)\s+([A-Za-z_][A-Za-z0-9_]*)\s*$/
2940
+ );
2941
+ if (pseudoMatch) {
2942
+ const kindKw = pseudoMatch[1];
2943
+ const id = pseudoMatch[2];
2944
+ const pkind = PSEUDO_KEYWORDS[kindKw];
2945
+ const node = {
2946
+ id,
2947
+ label: "",
2948
+ kind: "pseudo",
2949
+ pseudoKind: pkind,
2950
+ activities: [],
2951
+ children: [],
2952
+ parent: parent?.id
2953
+ };
2954
+ ctx.byId.set(id, node);
2955
+ if (parent) parent.children.push(node);
2956
+ else ctx.states.push(node);
2957
+ i++;
2958
+ continue;
2959
+ }
2960
+ if (parent) {
2961
+ const activity = parseActivityLine(text2);
2962
+ if (activity && (activity.kind === "entry" || activity.kind === "exit" || activity.kind === "do")) {
2963
+ parent.activities.push(activity);
2964
+ i++;
2965
+ continue;
2966
+ }
2967
+ }
2968
+ const noteSimple = text2.match(
2969
+ /^note\s+(left[_ ]of|right[_ ]of)\s+([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*)$/
2970
+ );
2971
+ if (noteSimple) {
2972
+ const side = noteSimple[1].startsWith("left") ? "left" : "right";
2973
+ const target = noteSimple[2];
2974
+ ctx.noteCounter += 1;
2975
+ ctx.notes.push({
2976
+ id: `__note_${ctx.noteCounter}`,
2977
+ target,
2978
+ side,
2979
+ text: noteSimple[3].trim()
2980
+ });
2981
+ i++;
2982
+ continue;
2983
+ }
2984
+ const noteBlockMermaid = text2.match(
2985
+ /^note\s+(left[_ ]of|right[_ ]of)\s+([A-Za-z_][A-Za-z0-9_]*)\s*$/
2986
+ );
2987
+ if (noteBlockMermaid) {
2988
+ const side = noteBlockMermaid[1].startsWith("left") ? "left" : "right";
2989
+ const target = noteBlockMermaid[2];
2990
+ const buf = [];
2991
+ i++;
2992
+ while (i < lines.length) {
2993
+ const t = lines[i].text;
2994
+ if (t === "end note" || t === "}") break;
2995
+ buf.push(t);
2996
+ i++;
2997
+ }
2998
+ if (i >= lines.length) {
2999
+ throw new StateParseError("Unterminated note block", ln.line);
3000
+ }
3001
+ i++;
3002
+ ctx.noteCounter += 1;
3003
+ ctx.notes.push({
3004
+ id: `__note_${ctx.noteCounter}`,
3005
+ target,
3006
+ side,
3007
+ text: buf.join("\n")
3008
+ });
3009
+ continue;
3010
+ }
3011
+ const noteBlockSchematex = text2.match(
3012
+ /^note\s+(left[_ ]of\s+|right[_ ]of\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*\{\s*$/
3013
+ );
3014
+ if (noteBlockSchematex) {
3015
+ const side = noteBlockSchematex[1]?.startsWith("left") ? "left" : "right";
3016
+ const target = noteBlockSchematex[2];
3017
+ const buf = [];
3018
+ i++;
3019
+ while (i < lines.length && lines[i].text !== "}") {
3020
+ buf.push(lines[i].text);
3021
+ i++;
3022
+ }
3023
+ if (i >= lines.length) {
3024
+ throw new StateParseError("Unterminated note block", ln.line);
3025
+ }
3026
+ i++;
3027
+ ctx.noteCounter += 1;
3028
+ ctx.notes.push({
3029
+ id: `__note_${ctx.noteCounter}`,
3030
+ target,
3031
+ side,
3032
+ text: buf.join("\n")
3033
+ });
3034
+ continue;
3035
+ }
3036
+ const transMatch = text2.match(
3037
+ /^(\[\*\]|[A-Za-z_][A-Za-z0-9_]*)\s*-+>\s*(\[\*\]|[A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*(.*))?$/
3038
+ );
3039
+ if (transMatch) {
3040
+ const fromTok = transMatch[1];
3041
+ const toTok = transMatch[2];
3042
+ const labelRaw = transMatch[3];
3043
+ const resolveTok = (tok, position) => {
3044
+ if (tok === "[*]") {
3045
+ if (position === "from") return ensureInitialAlias(ctx, parent).id;
3046
+ return ensureFinalAlias(ctx, parent).id;
3047
+ }
3048
+ return ensureSimpleState(ctx, tok, parent).id;
3049
+ };
3050
+ const fromId = resolveTok(fromTok, "from");
3051
+ const toId = resolveTok(toTok, "to");
3052
+ ctx.transCounter += 1;
3053
+ const tid = `t${ctx.transCounter}`;
3054
+ const parsedLabel = labelRaw ? parseTransitionLabel(labelRaw) : {};
3055
+ ctx.transitions.push({
3056
+ id: tid,
3057
+ from: fromId,
3058
+ to: toId,
3059
+ trigger: parsedLabel.trigger,
3060
+ guard: parsedLabel.guard,
3061
+ action: parsedLabel.action
3062
+ });
3063
+ i++;
3064
+ continue;
3065
+ }
3066
+ const labelOnlyMatch = text2.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.+)$/);
3067
+ if (labelOnlyMatch && isIdent(labelOnlyMatch[1])) {
3068
+ const node = ensureSimpleState(ctx, labelOnlyMatch[1], parent);
3069
+ node.label = unquote2(labelOnlyMatch[2].trim());
3070
+ i++;
3071
+ continue;
3072
+ }
3073
+ const bareIdent = text2.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*$/);
3074
+ if (bareIdent) {
3075
+ ensureSimpleState(ctx, bareIdent[1], parent);
3076
+ i++;
3077
+ continue;
3078
+ }
3079
+ throw new StateParseError(`Unparseable line: ${text2}`, ln.line);
3080
+ }
3081
+ if (compositeStack.length > 1) {
3082
+ throw new StateParseError("Unclosed composite block (expected '}')");
3083
+ }
3084
+ function finalizeRegions(node) {
3085
+ if (node.regions) {
3086
+ const consumed = node.regions.reduce((s, r) => s + r.length, 0);
3087
+ if (node.children.length > consumed) {
3088
+ node.regions.push(node.children.slice(consumed));
3089
+ }
3090
+ }
3091
+ for (const child of node.children) finalizeRegions(child);
3092
+ }
3093
+ for (const s of ctx.states) finalizeRegions(s);
3094
+ return {
3095
+ type: "state",
3096
+ title: title2,
3097
+ direction,
3098
+ states: ctx.states,
3099
+ transitions: ctx.transitions,
3100
+ notes: ctx.notes
3101
+ };
3102
+ }
3103
+
3104
+ // src/diagrams/state/layout.ts
3105
+ var PSEUDO_BBOX = {
3106
+ initial: { w: 22, h: 22 },
3107
+ final: { w: 28, h: 28 },
3108
+ choice: { w: 32, h: 32 },
3109
+ junction: { w: 16, h: 16 },
3110
+ fork: { w: 100, h: 8 },
3111
+ // horizontal bar — rotated for LR direction below
3112
+ join: { w: 100, h: 8 },
3113
+ history: { w: 26, h: 26 },
3114
+ dhistory: { w: 30, h: 30 },
3115
+ terminate: { w: 22, h: 22 },
3116
+ entry_point: { w: 18, h: 18 },
3117
+ exit_point: { w: 18, h: 18 }
3118
+ };
3119
+ var NOTE_W = 180;
3120
+ var NOTE_LINE_H = 14;
3121
+ function shapeForState(node) {
3122
+ if (node.kind === "simple") return "round";
3123
+ if (node.kind === "composite") return "round";
3124
+ switch (node.pseudoKind) {
3125
+ case "initial":
3126
+ case "final":
3127
+ case "junction":
3128
+ case "history":
3129
+ case "dhistory":
3130
+ case "terminate":
3131
+ case "entry_point":
3132
+ case "exit_point":
3133
+ return "circle";
3134
+ case "choice":
3135
+ return "diamond";
3136
+ case "fork":
3137
+ case "join":
3138
+ return "rect";
3139
+ default:
3140
+ return "round";
3141
+ }
3142
+ }
3143
+ function buildLabel(t) {
3144
+ const parts = [];
3145
+ if (t.trigger) parts.push(t.trigger);
3146
+ if (t.guard) parts.push(`[${t.guard}]`);
3147
+ let s = parts.join(" ");
3148
+ if (t.action) s = s ? `${s} / ${t.action}` : `/ ${t.action}`;
3149
+ return s.trim() || void 0;
3150
+ }
3151
+ function convertToFlowchart(ast) {
3152
+ const fcNodes = [];
3153
+ const fcSubgraphs = [];
3154
+ const pseudoSizes = /* @__PURE__ */ new Map();
3155
+ const compositeEntryFor = /* @__PURE__ */ new Map();
3156
+ const compositeExitFor = /* @__PURE__ */ new Map();
3157
+ const visit = (s, parentId) => {
3158
+ if (s.kind === "composite") {
3159
+ const childIds = [];
3160
+ const childSgIds = [];
3161
+ for (const child of s.children) {
3162
+ if (child.kind === "composite") childSgIds.push(child.id);
3163
+ else childIds.push(child.id);
3164
+ }
3165
+ let entryChild;
3166
+ let exitChild;
3167
+ for (const child of s.children) {
3168
+ if (child.kind === "pseudo" && child.pseudoKind === "initial" && !entryChild) {
3169
+ entryChild = child.id;
3170
+ }
3171
+ if (child.kind === "pseudo" && child.pseudoKind === "final") {
3172
+ exitChild = child.id;
3173
+ }
3174
+ }
3175
+ if (!entryChild) entryChild = s.children[0]?.id;
3176
+ if (!exitChild) exitChild = s.children[s.children.length - 1]?.id;
3177
+ if (entryChild) compositeEntryFor.set(s.id, entryChild);
3178
+ if (exitChild) compositeExitFor.set(s.id, exitChild);
3179
+ fcSubgraphs.push({
3180
+ id: s.id,
3181
+ label: s.label || s.id,
3182
+ direction: ast.direction,
3183
+ children: childIds,
3184
+ subgraphs: childSgIds
3185
+ });
3186
+ for (const child of s.children) visit(child, s.id);
3187
+ return;
3188
+ }
3189
+ const node = {
3190
+ id: s.id,
3191
+ label: labelForFlowchart(s),
3192
+ shape: shapeForState(s),
3193
+ parent: parentId
3194
+ };
3195
+ fcNodes.push(node);
3196
+ if (s.kind === "pseudo" && s.pseudoKind) {
3197
+ pseudoSizes.set(s.id, { ...PSEUDO_BBOX[s.pseudoKind] });
3198
+ }
3199
+ };
3200
+ for (const s of ast.states) visit(s, void 0);
3201
+ const fcEdges = ast.transitions.map((t) => {
3202
+ let from = t.from;
3203
+ let to = t.to;
3204
+ const fromExit = compositeExitFor.get(t.from);
3205
+ if (fromExit) from = fromExit;
3206
+ const toEntry = compositeEntryFor.get(t.to);
3207
+ if (toEntry) to = toEntry;
3208
+ return {
3209
+ id: t.id,
3210
+ from,
3211
+ to,
3212
+ kind: "solid",
3213
+ label: buildLabel(t)
3214
+ };
3215
+ });
3216
+ const fc = {
3217
+ type: "flowchart",
3218
+ title: ast.title,
3219
+ direction: ast.direction === "LR" ? "LR" : "TB",
3220
+ nodes: fcNodes,
3221
+ edges: fcEdges,
3222
+ subgraphs: fcSubgraphs,
3223
+ classDefs: [],
3224
+ linkStyles: /* @__PURE__ */ new Map()
3225
+ };
3226
+ return { ast: fc, pseudoSizes };
3227
+ }
3228
+ function labelForFlowchart(s) {
3229
+ if (s.kind === "pseudo") return "";
3230
+ if (s.activities.length === 0) return s.label || s.id;
3231
+ const activityWidth = s.activities.map((a) => activityText(a).length).reduce((m, n) => Math.max(m, n), 0);
3232
+ const label = s.label || s.id;
3233
+ return label.length >= activityWidth ? label : "x".repeat(activityWidth);
3234
+ }
3235
+ function activityText(a) {
3236
+ if (a.kind === "entry" || a.kind === "exit" || a.kind === "do") {
3237
+ return `${a.kind} / ${a.action ?? ""}`;
3238
+ }
3239
+ const parts = [];
3240
+ if (a.trigger) parts.push(a.trigger);
3241
+ if (a.guard) parts.push(`[${a.guard}]`);
3242
+ let s = parts.join(" ");
3243
+ if (a.action) s = s ? `${s} / ${a.action}` : `/ ${a.action}`;
3244
+ return s;
3245
+ }
3246
+ function wrapNoteText(text2, charsPerLine = 28) {
3247
+ const out = [];
3248
+ for (const para of text2.split(/\n/)) {
3249
+ const words = para.split(/\s+/);
3250
+ let cur = "";
3251
+ for (const w of words) {
3252
+ if (!cur) cur = w;
3253
+ else if (cur.length + w.length + 1 <= charsPerLine) cur += ` ${w}`;
3254
+ else {
3255
+ out.push(cur);
3256
+ cur = w;
3257
+ }
3258
+ }
3259
+ if (cur) out.push(cur);
3260
+ if (para === "") out.push("");
3261
+ }
3262
+ return out.length ? out : [""];
3263
+ }
3264
+ function selfLoopPath(cx, cy, w, h) {
3265
+ const startX = cx + w / 2;
3266
+ const startY = cy - h * 0.15;
3267
+ const endX = cx + w * 0.15;
3268
+ const endY = cy - h / 2;
3269
+ const c1x = startX + 28;
3270
+ const c1y = startY - 12;
3271
+ const c2x = endX + 28;
3272
+ const c2y = endY - 28;
3273
+ const path2 = `M ${startX} ${startY} C ${c1x} ${c1y}, ${c2x} ${c2y}, ${endX} ${endY}`;
3274
+ return { path: path2, labelX: startX + 28, labelY: startY - 18 };
3275
+ }
3276
+ function layoutStateDiagram(ast) {
3277
+ const { ast: fcAst, pseudoSizes } = convertToFlowchart(ast);
3278
+ const selfLoops = [];
3279
+ fcAst.edges = fcAst.edges.filter((e) => {
3280
+ if (e.from === e.to) {
3281
+ const t = ast.transitions.find((tr) => tr.id === e.id);
3282
+ if (t) selfLoops.push(t);
3283
+ return false;
3284
+ }
3285
+ return true;
3286
+ });
3287
+ const fcResult = layoutFlowchart(fcAst);
3288
+ const stateById = /* @__PURE__ */ new Map();
3289
+ const collectStates = (s) => {
3290
+ stateById.set(s.id, s);
3291
+ for (const c of s.children) collectStates(c);
3292
+ };
3293
+ for (const s of ast.states) collectStates(s);
3294
+ const stateNodes = [];
3295
+ for (const fcNode of fcResult.nodes) {
3296
+ if (fcNode.isDummy) continue;
3297
+ const s = stateById.get(fcNode.node.id);
3298
+ if (!s) continue;
3299
+ if (s.kind === "composite") continue;
3300
+ const cx = fcNode.x + fcNode.width / 2;
3301
+ const cy = fcNode.y + fcNode.height / 2;
3302
+ let w = fcNode.width;
3303
+ let h = fcNode.height;
3304
+ if (s.kind === "pseudo" && s.pseudoKind) {
3305
+ const ps = pseudoSizes.get(s.id);
3306
+ if (ps) {
3307
+ if ((s.pseudoKind === "fork" || s.pseudoKind === "join") && ast.direction === "LR") {
3308
+ w = 8;
3309
+ h = 100;
3310
+ } else {
3311
+ w = ps.w;
3312
+ h = ps.h;
3313
+ }
3314
+ }
3315
+ }
3316
+ stateNodes.push({
3317
+ id: s.id,
3318
+ x: cx - w / 2,
3319
+ y: cy - h / 2,
3320
+ width: w,
3321
+ height: h,
3322
+ cx,
3323
+ cy,
3324
+ layer: fcNode.layer,
3325
+ node: s,
3326
+ parent: s.parent
3327
+ });
3328
+ }
3329
+ const clusters = fcResult.clusters.map((c) => {
3330
+ const s = stateById.get(c.subgraph.id);
3331
+ if (!s) {
3332
+ return {
3333
+ id: c.subgraph.id,
3334
+ state: { id: c.subgraph.id, label: c.subgraph.label, kind: "composite", activities: [], children: [] },
3335
+ x: c.x,
3336
+ y: c.y,
3337
+ width: c.width,
3338
+ height: c.height
3339
+ };
3340
+ }
3341
+ return {
3342
+ id: c.subgraph.id,
3343
+ state: s,
3344
+ x: c.x,
3345
+ y: c.y,
3346
+ width: c.width,
3347
+ height: c.height
3348
+ };
3349
+ });
3350
+ const stateById2 = new Map(stateNodes.map((n) => [n.id, n]));
3351
+ const stateEdges = [];
3352
+ for (const fcEdge of fcResult.edges) {
3353
+ const t = ast.transitions.find((tr) => tr.id === fcEdge.edge.id);
3354
+ if (!t) continue;
3355
+ let path2 = fcEdge.path;
3356
+ const sourceNode = stateById2.get(fcEdge.edge.from);
3357
+ const targetNode = stateById2.get(fcEdge.edge.to);
3358
+ if (sourceNode && sourceNode.node.kind === "pseudo") {
3359
+ path2 = trimPathStart(path2, sourceNode.cx, sourceNode.cy, symbolRadius(sourceNode));
3360
+ }
3361
+ if (targetNode && targetNode.node.kind === "pseudo") {
3362
+ path2 = trimPathEnd(path2, targetNode.cx, targetNode.cy, symbolRadius(targetNode));
3363
+ }
3364
+ stateEdges.push({
3365
+ id: t.id,
3366
+ from: t.from,
3367
+ to: t.to,
3368
+ path: path2,
3369
+ label: buildLabel(t),
3370
+ labelX: fcEdge.labelAnchor?.x ?? 0,
3371
+ labelY: fcEdge.labelAnchor?.y ?? 0,
3372
+ labelAnchor: fcEdge.labelAnchor?.textAnchor ?? "middle"
3373
+ });
3374
+ }
3375
+ for (const sl of selfLoops) {
3376
+ const host = stateById2.get(sl.from);
3377
+ if (!host) continue;
3378
+ const { path: path2, labelX, labelY } = selfLoopPath(host.cx, host.cy, host.width, host.height);
3379
+ stateEdges.push({
3380
+ id: sl.id,
3381
+ from: sl.from,
3382
+ to: sl.to,
3383
+ path: path2,
3384
+ label: buildLabel(sl),
3385
+ labelX,
3386
+ labelY,
3387
+ labelAnchor: "start",
3388
+ selfLoop: true
3389
+ });
3390
+ }
3391
+ const notes = [];
3392
+ for (const note of ast.notes) {
3393
+ const target = stateById2.get(note.target);
3394
+ if (!target) continue;
3395
+ const lines = wrapNoteText(note.text);
3396
+ const w = NOTE_W;
3397
+ const h = lines.length * NOTE_LINE_H + 14;
3398
+ let x;
3399
+ let y;
3400
+ let leaderX1;
3401
+ let leaderX2;
3402
+ if (note.side === "left") {
3403
+ x = target.x - w - 24;
3404
+ y = target.cy - h / 2;
3405
+ leaderX1 = target.x;
3406
+ leaderX2 = x + w;
3407
+ } else {
3408
+ x = target.x + target.width + 24;
3409
+ y = target.cy - h / 2;
3410
+ leaderX1 = target.x + target.width;
3411
+ leaderX2 = x;
3412
+ }
3413
+ notes.push({
3414
+ note,
3415
+ x,
3416
+ y,
3417
+ width: w,
3418
+ height: h,
3419
+ lines,
3420
+ leader: { x1: leaderX1, y1: target.cy, x2: leaderX2, y2: y + h / 2 }
3421
+ });
3422
+ }
3423
+ let maxX = fcResult.width;
3424
+ let maxY = fcResult.height;
3425
+ let minX = 0;
3426
+ for (const n of notes) {
3427
+ maxX = Math.max(maxX, n.x + n.width + 8);
3428
+ maxY = Math.max(maxY, n.y + n.height + 8);
3429
+ minX = Math.min(minX, n.x - 8);
3430
+ }
3431
+ if (minX < 0) {
3432
+ const dx = -minX;
3433
+ for (const n of stateNodes) {
3434
+ n.x += dx;
3435
+ n.cx += dx;
3436
+ }
3437
+ for (const c of clusters) {
3438
+ c.x += dx;
3439
+ }
3440
+ for (const e of stateEdges) {
3441
+ e.path = shiftPathX(e.path, dx);
3442
+ e.labelX += dx;
3443
+ }
3444
+ for (const n of notes) {
3445
+ n.x += dx;
3446
+ n.leader.x1 += dx;
3447
+ n.leader.x2 += dx;
3448
+ }
3449
+ maxX += dx;
3450
+ }
3451
+ for (const e of stateEdges) {
3452
+ if (!e.selfLoop) continue;
3453
+ maxX = Math.max(maxX, e.labelX + 60);
3454
+ maxY = Math.max(maxY, e.labelY + 60);
3455
+ }
3456
+ const titleOffset = ast.title ? 28 : 0;
3457
+ if (titleOffset) {
3458
+ for (const n of stateNodes) {
3459
+ n.y += titleOffset;
3460
+ n.cy += titleOffset;
3461
+ }
3462
+ for (const c of clusters) {
3463
+ c.y += titleOffset;
3464
+ }
3465
+ for (const e of stateEdges) {
3466
+ e.path = shiftPathY(e.path, titleOffset);
3467
+ e.labelY += titleOffset;
3468
+ }
3469
+ for (const n of notes) {
3470
+ n.y += titleOffset;
3471
+ n.leader.y1 += titleOffset;
3472
+ n.leader.y2 += titleOffset;
3473
+ }
3474
+ maxY += titleOffset;
3475
+ }
3476
+ return {
3477
+ width: maxX,
3478
+ height: maxY,
3479
+ nodes: stateNodes,
3480
+ edges: stateEdges,
3481
+ clusters,
3482
+ notes,
3483
+ title: ast.title,
3484
+ direction: ast.direction
3485
+ };
3486
+ }
3487
+ function symbolRadius(node) {
3488
+ const k = node.node.pseudoKind;
3489
+ switch (k) {
3490
+ case "initial":
3491
+ return 8;
3492
+ case "final":
3493
+ return 11;
3494
+ case "choice":
3495
+ return 14;
3496
+ case "junction":
3497
+ return 6;
3498
+ case "history":
3499
+ return 12;
3500
+ case "dhistory":
3501
+ return 13;
3502
+ case "terminate":
3503
+ return 8;
3504
+ case "entry_point":
3505
+ case "exit_point":
3506
+ return 7;
3507
+ case "fork":
3508
+ case "join":
3509
+ return Math.min(node.width, node.height) / 2;
3510
+ default:
3511
+ return Math.min(node.width, node.height) / 2;
3512
+ }
3513
+ }
3514
+ function parsePathPoints(d) {
3515
+ if (/[CSQTA]/.test(d)) return null;
3516
+ const tokens = d.match(/[ML]\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)/g) ?? [];
3517
+ const out = [];
3518
+ for (const tok of tokens) {
3519
+ const m = tok.match(/[ML]\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)/);
3520
+ if (!m) continue;
3521
+ out.push({ x: parseFloat(m[1]), y: parseFloat(m[2]) });
3522
+ }
3523
+ return out;
3524
+ }
3525
+ function pointsToPath(pts) {
3526
+ if (pts.length === 0) return "";
3527
+ const head = `M ${pts[0].x} ${pts[0].y}`;
3528
+ const rest = pts.slice(1).map((p) => `L ${p.x} ${p.y}`).join(" ");
3529
+ return rest ? `${head} ${rest}` : head;
3530
+ }
3531
+ function trimPathStart(d, cx, cy, r) {
3532
+ const pts = parsePathPoints(d);
3533
+ if (!pts || pts.length < 2) return d;
3534
+ const p0 = pts[0];
3535
+ const p1 = pts[1];
3536
+ const newP0 = projectOnPerimeter(p0, p1, cx, cy, r);
3537
+ pts[0] = newP0;
3538
+ return pointsToPath(pts);
3539
+ }
3540
+ function trimPathEnd(d, cx, cy, r) {
3541
+ const pts = parsePathPoints(d);
3542
+ if (!pts || pts.length < 2) return d;
3543
+ const last = pts[pts.length - 1];
3544
+ const prev = pts[pts.length - 2];
3545
+ const newLast = projectOnPerimeter(last, prev, cx, cy, r);
3546
+ pts[pts.length - 1] = newLast;
3547
+ return pointsToPath(pts);
3548
+ }
3549
+ function projectOnPerimeter(endpoint, neighbor, cx, cy, r) {
3550
+ const dx = endpoint.x - neighbor.x;
3551
+ const dy = endpoint.y - neighbor.y;
3552
+ if (Math.abs(dx) > Math.abs(dy)) {
3553
+ const sign2 = dx >= 0 ? 1 : -1;
3554
+ return { x: cx - sign2 * r, y: cy };
3555
+ }
3556
+ const sign = dy >= 0 ? 1 : -1;
3557
+ return { x: cx, y: cy - sign * r };
3558
+ }
3559
+ function shiftPathX(d, dx) {
3560
+ return d.replace(/([MLCQ])\s*((?:-?\d+(?:\.\d+)?\s+){1,5}-?\d+(?:\.\d+)?)/g, (_, cmd, args) => {
3561
+ const nums = args.trim().split(/\s+/).map(Number);
3562
+ const out = [];
3563
+ for (let i = 0; i < nums.length; i++) {
3564
+ out.push(i % 2 === 0 ? nums[i] + dx : nums[i]);
3565
+ }
3566
+ return `${cmd} ${out.join(" ")}`;
3567
+ });
3568
+ }
3569
+ function shiftPathY(d, dy) {
3570
+ return d.replace(/([MLCQ])\s*((?:-?\d+(?:\.\d+)?\s+){1,5}-?\d+(?:\.\d+)?)/g, (_, cmd, args) => {
3571
+ const nums = args.trim().split(/\s+/).map(Number);
3572
+ const out = [];
3573
+ for (let i = 0; i < nums.length; i++) {
3574
+ out.push(i % 2 === 1 ? nums[i] + dy : nums[i]);
3575
+ }
3576
+ return `${cmd} ${out.join(" ")}`;
3577
+ });
3578
+ }
3579
+
3580
+ // src/diagrams/state/renderer.ts
3581
+ var ARROW_MARKER_ID = "lt-state-arrow";
3582
+ var STYLE = `
3583
+ .lt-state-body { fill: #ffffff; stroke: #2a2a2a; stroke-width: 1.6; }
3584
+ .lt-state-name { font: 600 12px system-ui, sans-serif; fill: #1a1a1a; }
3585
+ .lt-state-div { stroke: #2a2a2a; stroke-width: 1; }
3586
+ .lt-state-activity { font: 11px ui-monospace, monospace; fill: #444; }
3587
+
3588
+ .lt-composite-body { fill: #fafafa; stroke: #2a2a2a; stroke-width: 1.6; }
3589
+ .lt-composite-title { font: 600 12px system-ui, sans-serif; fill: #1a1a1a; }
3590
+ .lt-composite-titlebar { fill: #f0f0f0; stroke: #2a2a2a; stroke-width: 1; }
3591
+ .lt-region-div { stroke: #888; stroke-width: 1; stroke-dasharray: 6 4; }
3592
+
3593
+ .lt-ps-initial { fill: #1a1a1a; }
3594
+ .lt-ps-final-outer { fill: #ffffff; stroke: #1a1a1a; stroke-width: 1.6; }
3595
+ .lt-ps-final-inner { fill: #1a1a1a; }
3596
+ .lt-ps-choice { fill: #ffffff; stroke: #1a1a1a; stroke-width: 1.6; }
3597
+ .lt-ps-junction { fill: #1a1a1a; }
3598
+ .lt-ps-bar { fill: #1a1a1a; }
3599
+ .lt-ps-history-body { fill: #ffffff; stroke: #1a1a1a; stroke-width: 1.6; }
3600
+ .lt-ps-history-text { font: 600 11px serif; fill: #1a1a1a; }
3601
+ .lt-ps-terminate { stroke: #1a1a1a; stroke-width: 2; }
3602
+ .lt-ps-entrypoint { fill: #ffffff; stroke: #1a1a1a; stroke-width: 1.6; }
3603
+ .lt-ps-exitpoint { fill: #ffffff; stroke: #1a1a1a; stroke-width: 1.6; }
3604
+
3605
+ .lt-transition { stroke: #2a2a2a; stroke-width: 1.4; fill: none; }
3606
+ .lt-transition-label { font: 11px system-ui, sans-serif; fill: #1a1a1a; }
3607
+ .lt-transition-label-bg { fill: #ffffff; opacity: 0.92; }
3608
+
3609
+ .lt-note-body { fill: #fff8c5; stroke: #b79400; stroke-width: 1; }
3610
+ .lt-note-text { font: 11px system-ui, sans-serif; fill: #2a2a2a; }
3611
+ .lt-note-leader { stroke: #b79400; stroke-width: 1; stroke-dasharray: 3 3; fill: none; }
3612
+
3613
+ .lt-title { font: 600 14px system-ui, sans-serif; fill: #1a1a1a; }
3614
+ `;
3615
+ function renderArrowMarker() {
3616
+ return el(
3617
+ "marker",
3618
+ {
3619
+ id: ARROW_MARKER_ID,
3620
+ markerWidth: 10,
3621
+ markerHeight: 10,
3622
+ refX: 9,
3623
+ refY: 3,
3624
+ orient: "auto",
3625
+ markerUnits: "strokeWidth"
3626
+ },
3627
+ [polygon({ points: "0,0 10,3 0,6", fill: "#2a2a2a" })]
3628
+ );
3629
+ }
3630
+ function activityText2(a) {
3631
+ if (a.kind === "entry" || a.kind === "exit" || a.kind === "do") {
3632
+ return `${a.kind} / ${a.action ?? ""}`;
3633
+ }
3634
+ const parts = [];
3635
+ if (a.trigger) parts.push(a.trigger);
3636
+ if (a.guard) parts.push(`[${a.guard}]`);
3637
+ let s = parts.join(" ");
3638
+ if (a.action) s = s ? `${s} / ${a.action}` : `/ ${a.action}`;
3639
+ return s;
3640
+ }
3641
+ function renderSimple(node) {
3642
+ const { x, y, width, height } = node;
3643
+ const children = [
3644
+ rect({ x, y, width, height, rx: 8, ry: 8, class: "lt-state-body" })
3645
+ ];
3646
+ const label = node.node.label || node.id;
3647
+ if (node.node.activities.length === 0) {
3648
+ children.push(
3649
+ text(
3650
+ { x: x + width / 2, y: y + height / 2 + 4, "text-anchor": "middle", class: "lt-state-name" },
3651
+ label
3652
+ )
3653
+ );
3654
+ } else {
3655
+ children.push(
3656
+ text(
3657
+ { x: x + width / 2, y: y + 16, "text-anchor": "middle", class: "lt-state-name" },
3658
+ label
3659
+ )
3660
+ );
3661
+ children.push(
3662
+ line({ x1: x, y1: y + 22, x2: x + width, y2: y + 22, class: "lt-state-div" })
3663
+ );
3664
+ let cy = y + 36;
3665
+ for (const a of node.node.activities) {
3666
+ children.push(text({ x: x + 8, y: cy, class: "lt-state-activity" }, activityText2(a)));
3667
+ cy += 14;
3668
+ }
3669
+ }
3670
+ return group({ class: "lt-state lt-simple", "data-id": node.id }, children);
3671
+ }
3672
+ function renderComposite(c) {
3673
+ const { x, y, width, height } = c;
3674
+ const titleBarH = 22;
3675
+ const acts = c.state.activities;
3676
+ const actsH = acts.length ? acts.length * 14 + 6 : 0;
3677
+ const parts = [
3678
+ rect({ x, y, width, height, rx: 10, ry: 10, class: "lt-composite-body" }),
3679
+ // Title bar background — only the top strip
3680
+ path({
3681
+ d: `M ${x + 1} ${y + titleBarH} L ${x + width - 1} ${y + titleBarH}`,
3682
+ class: "lt-state-div"
3683
+ }),
3684
+ text(
3685
+ { x: x + 12, y: y + 16, class: "lt-composite-title" },
3686
+ c.state.label || c.state.id
3687
+ )
3688
+ ];
3689
+ if (acts.length) {
3690
+ let cy = y + titleBarH + 14;
3691
+ for (const a of acts) {
3692
+ parts.push(text({ x: x + 12, y: cy, class: "lt-state-activity" }, activityText2(a)));
3693
+ cy += 14;
3694
+ }
3695
+ parts.push(
3696
+ path({
3697
+ d: `M ${x + 1} ${y + titleBarH + actsH} L ${x + width - 1} ${y + titleBarH + actsH}`,
3698
+ class: "lt-state-div"
3699
+ })
3700
+ );
3701
+ }
3702
+ if (c.regionDividers) {
3703
+ for (const yy of c.regionDividers) {
3704
+ parts.push(
3705
+ line({ x1: x + 1, y1: yy, x2: x + width - 1, y2: yy, class: "lt-region-div" })
3706
+ );
3707
+ }
3708
+ }
3709
+ return group(
3710
+ { class: "lt-state lt-composite", "data-id": c.id },
3711
+ parts
3712
+ );
3713
+ }
3714
+ function renderPseudo(node) {
3715
+ const cx = node.cx;
3716
+ const cy = node.cy;
3717
+ const k = node.node.pseudoKind;
3718
+ switch (k) {
3719
+ case "initial":
3720
+ return group(
3721
+ { class: "lt-state lt-pseudo", "data-id": node.id, "data-kind": "initial" },
3722
+ [circle({ cx, cy, r: 8, class: "lt-ps-initial" })]
3723
+ );
3724
+ case "final":
3725
+ return group(
3726
+ { class: "lt-state lt-pseudo", "data-id": node.id, "data-kind": "final" },
3727
+ [
3728
+ circle({ cx, cy, r: 11, class: "lt-ps-final-outer" }),
3729
+ circle({ cx, cy, r: 6, class: "lt-ps-final-inner" })
3730
+ ]
3731
+ );
3732
+ case "choice": {
3733
+ const r = 14;
3734
+ return group(
3735
+ { class: "lt-state lt-pseudo", "data-id": node.id, "data-kind": "choice" },
3736
+ [
3737
+ polygon({
3738
+ points: `${cx},${cy - r} ${cx + r},${cy} ${cx},${cy + r} ${cx - r},${cy}`,
3739
+ class: "lt-ps-choice"
3740
+ })
3741
+ ]
3742
+ );
3743
+ }
3744
+ case "junction":
3745
+ return group(
3746
+ { class: "lt-state lt-pseudo", "data-id": node.id, "data-kind": "junction" },
3747
+ [circle({ cx, cy, r: 6, class: "lt-ps-junction" })]
3748
+ );
3749
+ case "fork":
3750
+ case "join":
3751
+ return group(
3752
+ { class: "lt-state lt-pseudo", "data-id": node.id, "data-kind": k },
3753
+ [rect({ x: node.x, y: node.y, width: node.width, height: node.height, rx: 2, ry: 2, class: "lt-ps-bar" })]
3754
+ );
3755
+ case "history":
3756
+ return group(
3757
+ { class: "lt-state lt-pseudo", "data-id": node.id, "data-kind": "history" },
3758
+ [
3759
+ circle({ cx, cy, r: 12, class: "lt-ps-history-body" }),
3760
+ text({ x: cx, y: cy + 4, "text-anchor": "middle", class: "lt-ps-history-text" }, "H")
3761
+ ]
3762
+ );
3763
+ case "dhistory":
3764
+ return group(
3765
+ { class: "lt-state lt-pseudo", "data-id": node.id, "data-kind": "dhistory" },
3766
+ [
3767
+ circle({ cx, cy, r: 13, class: "lt-ps-history-body" }),
3768
+ text({ x: cx, y: cy + 4, "text-anchor": "middle", class: "lt-ps-history-text" }, "H*")
3769
+ ]
3770
+ );
3771
+ case "terminate":
3772
+ return group(
3773
+ { class: "lt-state lt-pseudo", "data-id": node.id, "data-kind": "terminate" },
3774
+ [
3775
+ line({ x1: cx - 8, y1: cy - 8, x2: cx + 8, y2: cy + 8, class: "lt-ps-terminate" }),
3776
+ line({ x1: cx + 8, y1: cy - 8, x2: cx - 8, y2: cy + 8, class: "lt-ps-terminate" })
3777
+ ]
3778
+ );
3779
+ case "entry_point":
3780
+ return group(
3781
+ { class: "lt-state lt-pseudo", "data-id": node.id, "data-kind": "entry_point" },
3782
+ [circle({ cx, cy, r: 7, class: "lt-ps-entrypoint" })]
3783
+ );
3784
+ case "exit_point":
3785
+ return group(
3786
+ { class: "lt-state lt-pseudo", "data-id": node.id, "data-kind": "exit_point" },
3787
+ [
3788
+ circle({ cx, cy, r: 7, class: "lt-ps-exitpoint" }),
3789
+ line({ x1: cx - 4, y1: cy - 4, x2: cx + 4, y2: cy + 4, class: "lt-ps-terminate" }),
3790
+ line({ x1: cx + 4, y1: cy - 4, x2: cx - 4, y2: cy + 4, class: "lt-ps-terminate" })
3791
+ ]
3792
+ );
3793
+ default:
3794
+ return "";
3795
+ }
3796
+ }
3797
+ function renderNode(node) {
3798
+ if (node.node.kind === "pseudo") return renderPseudo(node);
3799
+ return renderSimple(node);
3800
+ }
3801
+ function renderEdge(edge) {
3802
+ const parts = [
3803
+ path({
3804
+ d: edge.path,
3805
+ class: "lt-transition",
3806
+ "marker-end": `url(#${ARROW_MARKER_ID})`,
3807
+ "data-from": edge.from,
3808
+ "data-to": edge.to
3809
+ })
3810
+ ];
3811
+ if (edge.label) {
3812
+ const w = Math.max(20, edge.label.length * 6.4 + 8);
3813
+ const anchor = edge.labelAnchor ?? "middle";
3814
+ const dx = anchor === "start" ? 0 : anchor === "end" ? -w : -w / 2;
3815
+ parts.push(
3816
+ rect({
3817
+ x: edge.labelX + dx,
3818
+ y: edge.labelY - 10,
3819
+ width: w,
3820
+ height: 14,
3821
+ rx: 2,
3822
+ ry: 2,
3823
+ class: "lt-transition-label-bg"
3824
+ })
3825
+ );
3826
+ parts.push(
3827
+ text(
3828
+ {
3829
+ x: edge.labelX,
3830
+ y: edge.labelY,
3831
+ "text-anchor": anchor,
3832
+ class: "lt-transition-label"
3833
+ },
3834
+ edge.label
3835
+ )
3836
+ );
3837
+ }
3838
+ return group({ class: "lt-edge", "data-edge-id": edge.id }, parts);
3839
+ }
3840
+ function renderNote(n) {
3841
+ const parts = [
3842
+ line({
3843
+ x1: n.leader.x1,
3844
+ y1: n.leader.y1,
3845
+ x2: n.leader.x2,
3846
+ y2: n.leader.y2,
3847
+ class: "lt-note-leader"
3848
+ }),
3849
+ rect({ x: n.x, y: n.y, width: n.width, height: n.height, rx: 4, ry: 4, class: "lt-note-body" })
3850
+ ];
3851
+ let yy = n.y + 14;
3852
+ for (const ln of n.lines) {
3853
+ parts.push(text({ x: n.x + 8, y: yy, class: "lt-note-text" }, ln));
3854
+ yy += 14;
3855
+ }
3856
+ return group({ class: "lt-note", "data-target": n.note.target }, parts);
3857
+ }
3858
+ function renderStateDiagram(ast, _config) {
3859
+ const layout = layoutStateDiagram(ast);
3860
+ return renderLayout(layout);
3861
+ }
3862
+ function renderLayout(layout) {
3863
+ const titleNode = layout.title ? text({ x: 16, y: 22, class: "lt-title" }, layout.title) : "";
3864
+ return svgRoot(
3865
+ {
3866
+ width: layout.width,
3867
+ height: layout.height,
3868
+ viewBox: `0 0 ${layout.width} ${layout.height}`,
3869
+ class: "lt-state",
3870
+ "data-diagram-type": "state"
3871
+ },
3872
+ [
3873
+ el("title", {}, escapeXml(`State Diagram${layout.title ? " \u2014 " + layout.title : ""}`)),
3874
+ el("desc", {}, "UML 2.5 / Harel statechart rendered by Schematex"),
3875
+ defs([renderArrowMarker(), el("style", {}, STYLE)]),
3876
+ titleNode,
3877
+ // Composite clusters first so simple-state bodies sit on top.
3878
+ group({ class: "lt-clusters" }, layout.clusters.map(renderComposite)),
3879
+ group({ class: "lt-state-bodies" }, layout.nodes.map(renderNode)),
3880
+ group({ class: "lt-edges" }, layout.edges.map(renderEdge)),
3881
+ group({ class: "lt-notes" }, layout.notes.map(renderNote))
3882
+ ]
3883
+ );
3884
+ }
3885
+
3886
+ // src/diagrams/state/index.ts
3887
+ var state = {
3888
+ type: "state",
3889
+ detect(text2) {
3890
+ return /^\s*state\b/i.test(text2);
3891
+ },
3892
+ parse: parseStateDiagram,
3893
+ render(text2, config) {
3894
+ const ast = parseStateDiagram(text2);
3895
+ return renderStateDiagram(ast);
3896
+ }
3897
+ };
3898
+
3899
+ // src/diagrams/pid/parser.ts
3900
+ var PidParseError = class extends Error {
3901
+ constructor(message, line2) {
3902
+ super(line2 !== void 0 ? `Line ${line2}: ${message}` : message);
3903
+ this.line = line2;
3904
+ this.name = "PidParseError";
3905
+ }
3906
+ line;
3907
+ };
3908
+ var EQUIP_TYPES = /* @__PURE__ */ new Set([
3909
+ "tank_atm",
3910
+ "tank_cone_roof",
3911
+ "vessel_v",
3912
+ "vessel_h",
3913
+ "sphere",
3914
+ "column_tray",
3915
+ "column_packed",
3916
+ "hx_shell_tube",
3917
+ "hx_air_cooled",
3918
+ "reboiler",
3919
+ "condenser",
3920
+ "pump_centrifugal",
3921
+ "pump_pd",
3922
+ "compressor",
3923
+ "blower",
3924
+ "reactor_cstr",
3925
+ "reactor_pfr",
3926
+ "filter",
3927
+ "cyclone",
3928
+ "flare",
3929
+ "cooling_tower",
3930
+ "valve_gate",
3931
+ "valve_ball",
3932
+ "valve_globe",
3933
+ "valve_butterfly",
3934
+ "valve_check",
3935
+ "valve_control",
3936
+ "valve_psv"
3937
+ ]);
3938
+ var LINE_TYPES = /* @__PURE__ */ new Set([
3939
+ "process",
3940
+ "process_minor",
3941
+ "pneumatic",
3942
+ "electric",
3943
+ "hydraulic",
3944
+ "capillary",
3945
+ "software",
3946
+ "mechanical"
3947
+ ]);
3948
+ var INST_CATEGORIES = /* @__PURE__ */ new Set([
3949
+ "field_discrete",
3950
+ "field_shared",
3951
+ "field_computer",
3952
+ "field_plc",
3953
+ "cr_discrete",
3954
+ "cr_shared",
3955
+ "cr_computer",
3956
+ "cr_plc",
3957
+ "local_discrete",
3958
+ "local_shared"
3959
+ ]);
3960
+ function preprocess4(src) {
3961
+ const out = [];
3962
+ const lines = src.split(/\r?\n/);
3963
+ for (let i = 0; i < lines.length; i++) {
3964
+ const raw = lines[i];
3965
+ if (raw === void 0) continue;
3966
+ let stripped = "";
3967
+ let inQuote = false;
3968
+ for (const ch of raw) {
3969
+ if (ch === '"') inQuote = !inQuote;
3970
+ if (ch === "#" && !inQuote) break;
3971
+ stripped += ch;
3972
+ }
3973
+ const trimmed = stripped.trim();
3974
+ if (!trimmed) continue;
3975
+ const indent = stripped.length - stripped.replace(/^\s+/, "").length;
3976
+ out.push({ text: trimmed, indent, line: i + 1 });
3977
+ }
3978
+ return out;
3979
+ }
3980
+ function unquote3(s) {
3981
+ const t = s.trim();
3982
+ if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
3983
+ return t.slice(1, -1).replace(/\\"/g, '"');
3984
+ }
3985
+ return t;
3986
+ }
3987
+ function parseAttrList(inside) {
3988
+ const out = {};
3989
+ const parts = [];
3990
+ let depth = 0;
3991
+ let inQuote = false;
3992
+ let cur = "";
3993
+ for (const ch of inside) {
3994
+ if (ch === '"') inQuote = !inQuote;
3995
+ if (!inQuote && ch === "[") depth++;
3996
+ if (!inQuote && ch === "]") depth--;
3997
+ if (!inQuote && depth === 0 && ch === ",") {
3998
+ parts.push(cur);
3999
+ cur = "";
4000
+ } else {
4001
+ cur += ch;
4002
+ }
4003
+ }
4004
+ if (cur.trim()) parts.push(cur);
4005
+ for (const p of parts) {
4006
+ const idx = p.indexOf(":");
4007
+ if (idx < 0) continue;
4008
+ const key = p.slice(0, idx).trim();
4009
+ const val = unquote3(p.slice(idx + 1).trim());
4010
+ out[key] = val;
4011
+ }
4012
+ return out;
4013
+ }
4014
+ function extractAttrs(text2) {
4015
+ let depth = 0;
4016
+ let inQuote = false;
4017
+ let openIdx = -1;
4018
+ for (let i = 0; i < text2.length; i++) {
4019
+ const ch = text2[i];
4020
+ if (ch === '"') inQuote = !inQuote;
4021
+ if (inQuote) continue;
4022
+ if (ch === "[") {
4023
+ if (depth === 0) openIdx = i;
4024
+ depth++;
4025
+ } else if (ch === "]") {
4026
+ depth--;
4027
+ if (depth === 0 && i === text2.length - 1) {
4028
+ const inside = text2.slice(openIdx + 1, i);
4029
+ return {
4030
+ rest: text2.slice(0, openIdx).trim(),
4031
+ attrs: parseAttrList(inside)
4032
+ };
4033
+ }
4034
+ }
4035
+ }
4036
+ return { rest: text2, attrs: {} };
4037
+ }
4038
+ function parseAnchor(tok) {
4039
+ const dot = tok.indexOf(".");
4040
+ if (dot < 0) return { id: tok };
4041
+ return { id: tok.slice(0, dot), port: tok.slice(dot + 1) };
4042
+ }
4043
+ function parsePid(src) {
4044
+ const lines = preprocess4(src);
4045
+ if (lines.length === 0) {
4046
+ throw new PidParseError("Empty document");
4047
+ }
4048
+ const header = lines[0];
4049
+ if (!/^pid\b/i.test(header.text)) {
4050
+ throw new PidParseError(`Expected 'pid' header, got '${header.text}'`, header.line);
4051
+ }
4052
+ let title2;
4053
+ let direction = "LR";
4054
+ const headerRest = header.text.replace(/^pid\b/i, "").trim();
4055
+ const { rest, attrs: headerAttrs } = extractAttrs(headerRest);
4056
+ if (headerAttrs.direction === "TB") direction = "TB";
4057
+ if (headerAttrs.direction === "LR") direction = "LR";
4058
+ if (rest) title2 = unquote3(rest);
4059
+ const equipment = [];
4060
+ const linesAst = [];
4061
+ const instruments = [];
4062
+ let currentInst;
4063
+ for (let idx = 1; idx < lines.length; idx++) {
4064
+ const ln = lines[idx];
4065
+ const text2 = ln.text;
4066
+ const measuresMatch = text2.match(/^measures\s+(.+)$/);
4067
+ if (measuresMatch && currentInst) {
4068
+ currentInst.measures = measuresMatch[1].trim();
4069
+ continue;
4070
+ }
4071
+ const controlsMatch = text2.match(/^controls\s+(.+)$/);
4072
+ if (controlsMatch && currentInst) {
4073
+ currentInst.controls = controlsMatch[1].trim();
4074
+ continue;
4075
+ }
4076
+ currentInst = void 0;
4077
+ const equipMatch = text2.match(/^equip\s+([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.+)$/);
4078
+ if (equipMatch) {
4079
+ const id = equipMatch[1];
4080
+ const tail = equipMatch[2];
4081
+ const { rest: typeRest, attrs } = extractAttrs(tail);
4082
+ const equipType = typeRest.trim();
4083
+ if (!EQUIP_TYPES.has(equipType)) {
4084
+ throw new PidParseError(`Unknown equipment type '${equipType}'`, ln.line);
4085
+ }
4086
+ equipment.push({
4087
+ id,
4088
+ equipType,
4089
+ tag: attrs.tag ?? id,
4090
+ attrs
4091
+ });
4092
+ continue;
4093
+ }
4094
+ const lineMatch = text2.match(/^line\s+([A-Za-z0-9_-]+)\s+from\s+(\S+)\s+to\s+(\S+)\s*(.*)$/);
4095
+ if (lineMatch) {
4096
+ const id = lineMatch[1];
4097
+ const fromTok = lineMatch[2];
4098
+ const toTok = lineMatch[3];
4099
+ const tail = lineMatch[4];
4100
+ const { attrs } = extractAttrs(tail);
4101
+ const lt = attrs.type ?? "process";
4102
+ if (!LINE_TYPES.has(lt)) {
4103
+ throw new PidParseError(`Unknown line type '${lt}'`, ln.line);
4104
+ }
4105
+ linesAst.push({
4106
+ id,
4107
+ from: parseAnchor(fromTok),
4108
+ to: parseAnchor(toTok),
4109
+ lineType: lt,
4110
+ tag: attrs.tag,
4111
+ size: attrs.size,
4112
+ service: attrs.service,
4113
+ attrs
4114
+ });
4115
+ continue;
4116
+ }
4117
+ const instMatch = text2.match(/^inst\s+([A-Z][A-Z0-9]*-[A-Za-z0-9]+)\s*:\s*(.+)$/);
4118
+ if (instMatch) {
4119
+ const tag = instMatch[1];
4120
+ const { rest: catRest, attrs } = extractAttrs(instMatch[2]);
4121
+ const category = catRest.trim();
4122
+ if (!INST_CATEGORIES.has(category)) {
4123
+ throw new PidParseError(`Unknown instrument category '${category}'`, ln.line);
4124
+ }
4125
+ const inst = {
4126
+ tag,
4127
+ category,
4128
+ attrs
4129
+ };
4130
+ instruments.push(inst);
4131
+ currentInst = inst;
4132
+ continue;
4133
+ }
4134
+ throw new PidParseError(`Unparseable line: ${text2}`, ln.line);
4135
+ }
4136
+ return {
4137
+ type: "pid",
4138
+ title: title2,
4139
+ direction,
4140
+ equipment,
4141
+ lines: linesAst,
4142
+ instruments
4143
+ };
4144
+ }
4145
+
4146
+ // src/diagrams/pid/symbols.ts
4147
+ var GEOMETRY = {
4148
+ // ── Tanks & vessels ─────────────────────────────────────
4149
+ tank_atm: {
4150
+ width: 90,
4151
+ height: 90,
4152
+ ports: {
4153
+ top: { x: 0, y: -45 },
4154
+ bottom: { x: 0, y: 45 },
4155
+ left: { x: -45, y: 0 },
4156
+ right: { x: 45, y: 0 },
4157
+ in: { x: -45, y: 0 },
4158
+ out: { x: 45, y: 0 }
4159
+ }
4160
+ },
4161
+ tank_cone_roof: {
4162
+ width: 90,
4163
+ height: 100,
4164
+ ports: {
4165
+ top: { x: 0, y: -50 },
4166
+ bottom: { x: 0, y: 50 },
4167
+ left: { x: -45, y: 0 },
4168
+ right: { x: 45, y: 0 }
4169
+ }
4170
+ },
4171
+ vessel_v: {
4172
+ width: 70,
4173
+ height: 130,
4174
+ ports: {
4175
+ top: { x: 0, y: -65 },
4176
+ bottom: { x: 0, y: 65 },
4177
+ left: { x: -35, y: 0 },
4178
+ right: { x: 35, y: 0 },
4179
+ in: { x: -35, y: -25 },
4180
+ out: { x: 35, y: 25 }
4181
+ }
4182
+ },
4183
+ vessel_h: {
4184
+ width: 130,
4185
+ height: 70,
4186
+ ports: {
4187
+ top: { x: 0, y: -35 },
4188
+ bottom: { x: 0, y: 35 },
4189
+ left: { x: -65, y: 0 },
4190
+ right: { x: 65, y: 0 },
4191
+ in: { x: -65, y: 0 },
4192
+ out: { x: 65, y: 0 }
4193
+ }
4194
+ },
4195
+ sphere: {
4196
+ width: 90,
4197
+ height: 90,
4198
+ ports: {
4199
+ top: { x: 0, y: -45 },
4200
+ bottom: { x: 0, y: 45 },
4201
+ left: { x: -45, y: 0 },
4202
+ right: { x: 45, y: 0 }
4203
+ }
4204
+ },
4205
+ column_tray: {
4206
+ width: 60,
4207
+ height: 180,
4208
+ ports: {
4209
+ top: { x: 0, y: -90 },
4210
+ bottom: { x: 0, y: 90 },
4211
+ feed: { x: -30, y: 0 },
4212
+ reflux: { x: 0, y: -90 },
4213
+ bottom_return: { x: -30, y: 70 },
4214
+ vapor_out: { x: 0, y: -90 },
4215
+ liquid_out: { x: 0, y: 90 }
4216
+ }
4217
+ },
4218
+ column_packed: {
4219
+ width: 60,
4220
+ height: 180,
4221
+ ports: {
4222
+ top: { x: 0, y: -90 },
4223
+ bottom: { x: 0, y: 90 },
4224
+ feed: { x: -30, y: 0 }
4225
+ }
4226
+ },
4227
+ hx_shell_tube: {
4228
+ width: 130,
4229
+ height: 60,
4230
+ ports: {
4231
+ shell_in: { x: 0, y: -30 },
4232
+ shell_out: { x: 0, y: 30 },
4233
+ tube_in: { x: -65, y: 0 },
4234
+ tube_out: { x: 65, y: 0 },
4235
+ in: { x: -65, y: 0 },
4236
+ out: { x: 65, y: 0 }
4237
+ }
4238
+ },
4239
+ hx_air_cooled: {
4240
+ width: 130,
4241
+ height: 80,
4242
+ ports: {
4243
+ in: { x: -65, y: 10 },
4244
+ out: { x: 65, y: 10 }
4245
+ }
4246
+ },
4247
+ reboiler: {
4248
+ width: 110,
4249
+ height: 60,
4250
+ ports: {
4251
+ in: { x: -55, y: 0 },
4252
+ out: { x: 55, y: 0 },
4253
+ bottom: { x: 0, y: 30 }
4254
+ }
4255
+ },
4256
+ condenser: {
4257
+ width: 130,
4258
+ height: 60,
4259
+ ports: {
4260
+ shell_in: { x: -50, y: -30 },
4261
+ shell_out: { x: 50, y: 30 },
4262
+ tube_in: { x: -65, y: 0 },
4263
+ tube_out: { x: 65, y: 0 },
4264
+ in: { x: -65, y: 0 },
4265
+ out: { x: 65, y: 0 }
4266
+ }
4267
+ },
4268
+ pump_centrifugal: {
4269
+ width: 70,
4270
+ height: 60,
4271
+ ports: {
4272
+ in: { x: -30, y: 0 },
4273
+ out: { x: 28, y: -22 },
4274
+ top: { x: 28, y: -22 },
4275
+ left: { x: -30, y: 0 },
4276
+ right: { x: 28, y: -22 }
4277
+ }
4278
+ },
4279
+ pump_pd: {
4280
+ width: 70,
4281
+ height: 60,
4282
+ ports: {
4283
+ in: { x: -35, y: 0 },
4284
+ out: { x: 35, y: 0 }
4285
+ }
4286
+ },
4287
+ compressor: {
4288
+ width: 90,
4289
+ height: 60,
4290
+ ports: {
4291
+ in: { x: -45, y: 0 },
4292
+ out: { x: 45, y: 0 }
4293
+ }
4294
+ },
4295
+ blower: {
4296
+ width: 70,
4297
+ height: 60,
4298
+ ports: {
4299
+ in: { x: -35, y: 0 },
4300
+ out: { x: 35, y: 0 }
4301
+ }
4302
+ },
4303
+ reactor_cstr: {
4304
+ width: 90,
4305
+ height: 110,
4306
+ ports: {
4307
+ top: { x: 0, y: -55 },
4308
+ bottom: { x: 0, y: 55 },
4309
+ in: { x: -45, y: -10 },
4310
+ out: { x: 0, y: 55 }
4311
+ }
4312
+ },
4313
+ reactor_pfr: {
4314
+ width: 130,
4315
+ height: 50,
4316
+ ports: {
4317
+ in: { x: -65, y: 0 },
4318
+ out: { x: 65, y: 0 }
4319
+ }
4320
+ },
4321
+ filter: {
4322
+ width: 70,
4323
+ height: 70,
4324
+ ports: {
4325
+ in: { x: -35, y: 0 },
4326
+ out: { x: 35, y: 0 }
4327
+ }
4328
+ },
4329
+ cyclone: {
4330
+ width: 70,
4331
+ height: 100,
4332
+ ports: {
4333
+ top: { x: 0, y: -50 },
4334
+ in: { x: -35, y: -30 },
4335
+ bottom: { x: 0, y: 50 },
4336
+ out: { x: 0, y: -50 }
4337
+ }
4338
+ },
4339
+ flare: {
4340
+ width: 30,
4341
+ height: 110,
4342
+ ports: {
4343
+ top: { x: 0, y: -55 },
4344
+ bottom: { x: 0, y: 55 },
4345
+ in: { x: 0, y: 55 }
4346
+ }
4347
+ },
4348
+ cooling_tower: {
4349
+ width: 100,
4350
+ height: 90,
4351
+ ports: {
4352
+ top: { x: 0, y: -45 },
4353
+ in: { x: -50, y: 0 },
4354
+ out: { x: 50, y: 0 }
4355
+ }
4356
+ },
4357
+ // ── Valves (in-line) ──────────────────────────────────
4358
+ valve_gate: {
4359
+ width: 36,
4360
+ height: 22,
4361
+ ports: { in: { x: -18, y: 0 }, out: { x: 18, y: 0 }, left: { x: -18, y: 0 }, right: { x: 18, y: 0 } }
4362
+ },
4363
+ valve_ball: {
4364
+ width: 36,
4365
+ height: 22,
4366
+ ports: { in: { x: -18, y: 0 }, out: { x: 18, y: 0 } }
4367
+ },
4368
+ valve_globe: {
4369
+ width: 36,
4370
+ height: 28,
4371
+ ports: { in: { x: -18, y: 0 }, out: { x: 18, y: 0 } }
4372
+ },
4373
+ valve_butterfly: {
4374
+ width: 36,
4375
+ height: 22,
4376
+ ports: { in: { x: -18, y: 0 }, out: { x: 18, y: 0 } }
4377
+ },
4378
+ valve_check: {
4379
+ width: 36,
4380
+ height: 22,
4381
+ ports: { in: { x: -18, y: 0 }, out: { x: 18, y: 0 } }
4382
+ },
4383
+ valve_control: {
4384
+ width: 36,
4385
+ height: 60,
4386
+ ports: { in: { x: -18, y: 12 }, out: { x: 18, y: 12 }, top: { x: 0, y: -22 } }
4387
+ },
4388
+ valve_psv: {
4389
+ width: 36,
4390
+ height: 60,
4391
+ ports: { in: { x: -18, y: 12 }, out: { x: 18, y: -8 } }
4392
+ }
4393
+ };
4394
+ var STROKE_BLACK = "#1d1d1d";
4395
+ var FILL_WHITE = "#ffffff";
4396
+ function bowtie() {
4397
+ return polygon({
4398
+ points: "-18,-11 0,0 -18,11 18,11 0,0 18,-11",
4399
+ class: "lt-pid-valve-body"
4400
+ });
4401
+ }
4402
+ function renderEquip(type, label) {
4403
+ switch (type) {
4404
+ case "tank_atm": {
4405
+ const w = 90;
4406
+ const h = 90;
4407
+ const cylTop = -h / 2 + 14;
4408
+ const parts = [
4409
+ // dome top
4410
+ path({
4411
+ d: `M ${-w / 2} ${cylTop} A ${w / 2} 14 0 0 1 ${w / 2} ${cylTop}`,
4412
+ class: "lt-pid-equip"
4413
+ }),
4414
+ // body
4415
+ rect({
4416
+ x: -w / 2,
4417
+ y: cylTop,
4418
+ width: w,
4419
+ height: h - 14,
4420
+ class: "lt-pid-equip"
4421
+ }),
4422
+ // tag
4423
+ text(
4424
+ { x: 0, y: 4, "text-anchor": "middle", class: "lt-pid-equip-tag" },
4425
+ label
4426
+ )
4427
+ ];
4428
+ return group({ class: "lt-pid-equip-group" }, parts);
4429
+ }
4430
+ case "tank_cone_roof": {
4431
+ const w = 90;
4432
+ const h = 100;
4433
+ const roofH = 18;
4434
+ const top = -h / 2;
4435
+ return group({}, [
4436
+ polygon({
4437
+ points: `${-w / 2},${top + roofH} 0,${top} ${w / 2},${top + roofH}`,
4438
+ class: "lt-pid-equip"
4439
+ }),
4440
+ rect({
4441
+ x: -w / 2,
4442
+ y: top + roofH,
4443
+ width: w,
4444
+ height: h - roofH,
4445
+ class: "lt-pid-equip"
4446
+ }),
4447
+ text({ x: 0, y: 4, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4448
+ ]);
4449
+ }
4450
+ case "vessel_v": {
4451
+ const w = 70;
4452
+ const h = 130;
4453
+ const headH = 16;
4454
+ const top = -h / 2;
4455
+ const bot = h / 2;
4456
+ return group({}, [
4457
+ path({
4458
+ d: `M ${-w / 2} ${top + headH}
4459
+ A ${w / 2} ${headH} 0 0 1 ${w / 2} ${top + headH}
4460
+ L ${w / 2} ${bot - headH}
4461
+ A ${w / 2} ${headH} 0 0 1 ${-w / 2} ${bot - headH}
4462
+ Z`,
4463
+ class: "lt-pid-equip"
4464
+ }),
4465
+ text(
4466
+ { x: 0, y: 4, "text-anchor": "middle", class: "lt-pid-equip-tag" },
4467
+ label
4468
+ )
4469
+ ]);
4470
+ }
4471
+ case "vessel_h": {
4472
+ const w = 130;
4473
+ const h = 70;
4474
+ const headW = 14;
4475
+ const left = -w / 2;
4476
+ const right = w / 2;
4477
+ return group({}, [
4478
+ path({
4479
+ d: `M ${left + headW} ${-h / 2}
4480
+ L ${right - headW} ${-h / 2}
4481
+ A ${headW} ${h / 2} 0 0 1 ${right - headW} ${h / 2}
4482
+ L ${left + headW} ${h / 2}
4483
+ A ${headW} ${h / 2} 0 0 1 ${left + headW} ${-h / 2}
4484
+ Z`,
4485
+ class: "lt-pid-equip"
4486
+ }),
4487
+ text(
4488
+ { x: 0, y: 4, "text-anchor": "middle", class: "lt-pid-equip-tag" },
4489
+ label
4490
+ )
4491
+ ]);
4492
+ }
4493
+ case "sphere": {
4494
+ return group({}, [
4495
+ circle({ cx: 0, cy: 0, r: 45, class: "lt-pid-equip" }),
4496
+ text({ x: 0, y: 4, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4497
+ ]);
4498
+ }
4499
+ case "column_tray": {
4500
+ const w = 60;
4501
+ const h = 180;
4502
+ const headH = 12;
4503
+ const trays = 12;
4504
+ const inner = h - headH * 2;
4505
+ const traySpacing = inner / (trays + 1);
4506
+ const trayLines = [];
4507
+ for (let i = 1; i <= trays; i++) {
4508
+ const y = -h / 2 + headH + traySpacing * i;
4509
+ trayLines.push(
4510
+ line({
4511
+ x1: -w / 2 + 6,
4512
+ y1: y,
4513
+ x2: w / 2 - 6,
4514
+ y2: y,
4515
+ class: "lt-pid-tray-line"
4516
+ })
4517
+ );
4518
+ }
4519
+ return group({}, [
4520
+ path({
4521
+ d: `M ${-w / 2} ${-h / 2 + headH}
4522
+ A ${w / 2} ${headH} 0 0 1 ${w / 2} ${-h / 2 + headH}
4523
+ L ${w / 2} ${h / 2 - headH}
4524
+ A ${w / 2} ${headH} 0 0 1 ${-w / 2} ${h / 2 - headH}
4525
+ Z`,
4526
+ class: "lt-pid-equip"
4527
+ }),
4528
+ ...trayLines,
4529
+ text(
4530
+ {
4531
+ x: 0,
4532
+ y: -h / 2 - 6,
4533
+ "text-anchor": "middle",
4534
+ class: "lt-pid-equip-tag"
4535
+ },
4536
+ label
4537
+ )
4538
+ ]);
4539
+ }
4540
+ case "column_packed": {
4541
+ const w = 60;
4542
+ const h = 180;
4543
+ const headH = 12;
4544
+ const packing = path({
4545
+ d: `M ${-w / 2 + 6} ${-h / 2 + headH + 6}
4546
+ L ${w / 2 - 6} ${h / 2 - headH - 6}
4547
+ M ${w / 2 - 6} ${-h / 2 + headH + 6}
4548
+ L ${-w / 2 + 6} ${h / 2 - headH - 6}`,
4549
+ class: "lt-pid-tray-line"
4550
+ });
4551
+ return group({}, [
4552
+ path({
4553
+ d: `M ${-w / 2} ${-h / 2 + headH}
4554
+ A ${w / 2} ${headH} 0 0 1 ${w / 2} ${-h / 2 + headH}
4555
+ L ${w / 2} ${h / 2 - headH}
4556
+ A ${w / 2} ${headH} 0 0 1 ${-w / 2} ${h / 2 - headH}
4557
+ Z`,
4558
+ class: "lt-pid-equip"
4559
+ }),
4560
+ packing,
4561
+ text(
4562
+ {
4563
+ x: 0,
4564
+ y: -h / 2 - 6,
4565
+ "text-anchor": "middle",
4566
+ class: "lt-pid-equip-tag"
4567
+ },
4568
+ label
4569
+ )
4570
+ ]);
4571
+ }
4572
+ case "hx_shell_tube": {
4573
+ const w = 130;
4574
+ const h = 60;
4575
+ const headW = 12;
4576
+ const left = -w / 2;
4577
+ const right = w / 2;
4578
+ const tubes = [];
4579
+ for (let yy = -h / 2 + 12; yy <= h / 2 - 12; yy += 8) {
4580
+ tubes.push(
4581
+ line({
4582
+ x1: left + headW + 4,
4583
+ y1: yy,
4584
+ x2: right - headW - 4,
4585
+ y2: yy,
4586
+ class: "lt-pid-tray-line"
4587
+ })
4588
+ );
4589
+ }
4590
+ return group({}, [
4591
+ path({
4592
+ d: `M ${left + headW} ${-h / 2}
4593
+ L ${right - headW} ${-h / 2}
4594
+ A ${headW} ${h / 2} 0 0 1 ${right - headW} ${h / 2}
4595
+ L ${left + headW} ${h / 2}
4596
+ A ${headW} ${h / 2} 0 0 1 ${left + headW} ${-h / 2}
4597
+ Z`,
4598
+ class: "lt-pid-equip"
4599
+ }),
4600
+ ...tubes,
4601
+ text(
4602
+ {
4603
+ x: 0,
4604
+ y: h / 2 + 14,
4605
+ "text-anchor": "middle",
4606
+ class: "lt-pid-equip-tag"
4607
+ },
4608
+ label
4609
+ )
4610
+ ]);
4611
+ }
4612
+ case "hx_air_cooled": {
4613
+ const w = 130;
4614
+ const h = 80;
4615
+ return group({}, [
4616
+ rect({
4617
+ x: -w / 2,
4618
+ y: -h / 2 + 18,
4619
+ width: w,
4620
+ height: h - 18,
4621
+ class: "lt-pid-equip"
4622
+ }),
4623
+ circle({ cx: 0, cy: -h / 2 + 14, r: 18, class: "lt-pid-equip" }),
4624
+ // 3-blade fan
4625
+ path({
4626
+ d: `M 0 ${-h / 2 + 14} L 14 ${-h / 2 + 6}
4627
+ M 0 ${-h / 2 + 14} L -14 ${-h / 2 + 6}
4628
+ M 0 ${-h / 2 + 14} L 0 ${-h / 2 + 30}`,
4629
+ class: "lt-pid-tray-line"
4630
+ }),
4631
+ text(
4632
+ { x: 0, y: h / 2 + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" },
4633
+ label
4634
+ )
4635
+ ]);
4636
+ }
4637
+ case "reboiler": {
4638
+ const w = 110;
4639
+ const h = 60;
4640
+ const headW = 14;
4641
+ return group({}, [
4642
+ path({
4643
+ d: `M ${-w / 2 + headW} ${-h / 2}
4644
+ L ${w / 2 - headW} ${-h / 2}
4645
+ A ${headW} ${h / 2} 0 0 1 ${w / 2 - headW} ${h / 2}
4646
+ L ${-w / 2 + headW} ${h / 2}
4647
+ A ${headW} ${h / 2} 0 0 1 ${-w / 2 + headW} ${-h / 2}
4648
+ Z`,
4649
+ class: "lt-pid-equip"
4650
+ }),
4651
+ line({
4652
+ x1: -w / 2 + headW + 4,
4653
+ y1: -10,
4654
+ x2: w / 2 - headW - 4,
4655
+ y2: -10,
4656
+ class: "lt-pid-tray-line"
4657
+ }),
4658
+ line({
4659
+ x1: -w / 2 + headW + 4,
4660
+ y1: 0,
4661
+ x2: w / 2 - headW - 4,
4662
+ y2: 0,
4663
+ class: "lt-pid-tray-line"
4664
+ }),
4665
+ line({
4666
+ x1: -w / 2 + headW + 4,
4667
+ y1: 10,
4668
+ x2: w / 2 - headW - 4,
4669
+ y2: 10,
4670
+ class: "lt-pid-tray-line"
4671
+ }),
4672
+ text({ x: 0, y: h / 2 + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4673
+ ]);
4674
+ }
4675
+ case "condenser": {
4676
+ const w = 130;
4677
+ const h = 60;
4678
+ const headW = 12;
4679
+ const left = -w / 2;
4680
+ const right = w / 2;
4681
+ const tubes = [];
4682
+ for (let yy = -h / 2 + 12; yy <= h / 2 - 12; yy += 10) {
4683
+ tubes.push(
4684
+ line({
4685
+ x1: left + headW + 4,
4686
+ y1: yy,
4687
+ x2: right - headW - 4,
4688
+ y2: yy,
4689
+ class: "lt-pid-tray-line"
4690
+ })
4691
+ );
4692
+ }
4693
+ return group({}, [
4694
+ path({
4695
+ d: `M ${left + headW} ${-h / 2}
4696
+ L ${right - headW} ${-h / 2}
4697
+ A ${headW} ${h / 2} 0 0 1 ${right - headW} ${h / 2}
4698
+ L ${left + headW} ${h / 2}
4699
+ A ${headW} ${h / 2} 0 0 1 ${left + headW} ${-h / 2}
4700
+ Z`,
4701
+ class: "lt-pid-equip"
4702
+ }),
4703
+ ...tubes,
4704
+ text({ x: 0, y: h / 2 + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4705
+ ]);
4706
+ }
4707
+ case "pump_centrifugal": {
4708
+ const r = 22;
4709
+ return group({}, [
4710
+ circle({ cx: 0, cy: 0, r, class: "lt-pid-equip" }),
4711
+ polygon({
4712
+ points: `${r * 0.4},${-r * 0.9} ${r + 6},${-r * 0.9} ${r * 0.4},${0}`,
4713
+ class: "lt-pid-equip"
4714
+ }),
4715
+ text(
4716
+ { x: 0, y: r + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" },
4717
+ label
4718
+ )
4719
+ ]);
4720
+ }
4721
+ case "pump_pd": {
4722
+ const r = 22;
4723
+ return group({}, [
4724
+ circle({ cx: 0, cy: 0, r, class: "lt-pid-equip" }),
4725
+ circle({ cx: -8, cy: 0, r: 6, class: "lt-pid-tray-line", fill: "none" }),
4726
+ circle({ cx: 8, cy: 0, r: 6, class: "lt-pid-tray-line", fill: "none" }),
4727
+ text(
4728
+ { x: 0, y: r + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" },
4729
+ label
4730
+ )
4731
+ ]);
4732
+ }
4733
+ case "compressor": {
4734
+ return group({}, [
4735
+ polygon({
4736
+ points: "-45,-20 45,-12 45,12 -45,20",
4737
+ class: "lt-pid-equip"
4738
+ }),
4739
+ text({ x: 0, y: 36, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4740
+ ]);
4741
+ }
4742
+ case "blower": {
4743
+ const r = 22;
4744
+ return group({}, [
4745
+ circle({ cx: 0, cy: 0, r, class: "lt-pid-equip" }),
4746
+ path({
4747
+ d: `M 0 0 L ${r * 0.8} ${-r * 0.5} M 0 0 L ${-r * 0.6} ${-r * 0.6} M 0 0 L 0 ${r * 0.8}`,
4748
+ class: "lt-pid-tray-line"
4749
+ }),
4750
+ text({ x: 0, y: r + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4751
+ ]);
4752
+ }
4753
+ case "reactor_cstr": {
4754
+ const w = 90;
4755
+ const h = 110;
4756
+ const headH = 14;
4757
+ const top = -h / 2;
4758
+ const bot = h / 2;
4759
+ return group({}, [
4760
+ path({
4761
+ d: `M ${-w / 2} ${top + headH}
4762
+ A ${w / 2} ${headH} 0 0 1 ${w / 2} ${top + headH}
4763
+ L ${w / 2} ${bot - headH}
4764
+ A ${w / 2} ${headH} 0 0 1 ${-w / 2} ${bot - headH}
4765
+ Z`,
4766
+ class: "lt-pid-equip"
4767
+ }),
4768
+ // agitator shaft + paddle
4769
+ line({ x1: 0, y1: top - 14, x2: 0, y2: 4, class: "lt-pid-tray-line" }),
4770
+ rect({ x: -10, y: 4, width: 20, height: 4, class: "lt-pid-equip" }),
4771
+ text(
4772
+ { x: 0, y: bot + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" },
4773
+ label
4774
+ )
4775
+ ]);
4776
+ }
4777
+ case "reactor_pfr": {
4778
+ const w = 130;
4779
+ const h = 50;
4780
+ const headW = 12;
4781
+ const left = -w / 2;
4782
+ const right = w / 2;
4783
+ const dots = [];
4784
+ for (let xx = left + headW + 8; xx <= right - headW - 8; xx += 12) {
4785
+ for (let yy = -h / 2 + 12; yy <= h / 2 - 8; yy += 10) {
4786
+ dots.push(circle({ cx: xx, cy: yy, r: 1.6, fill: STROKE_BLACK }));
4787
+ }
4788
+ }
4789
+ return group({}, [
4790
+ path({
4791
+ d: `M ${left + headW} ${-h / 2}
4792
+ L ${right - headW} ${-h / 2}
4793
+ A ${headW} ${h / 2} 0 0 1 ${right - headW} ${h / 2}
4794
+ L ${left + headW} ${h / 2}
4795
+ A ${headW} ${h / 2} 0 0 1 ${left + headW} ${-h / 2}
4796
+ Z`,
4797
+ class: "lt-pid-equip"
4798
+ }),
4799
+ ...dots,
4800
+ text({ x: 0, y: h / 2 + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4801
+ ]);
4802
+ }
4803
+ case "filter": {
4804
+ const w = 70;
4805
+ const h = 70;
4806
+ return group({}, [
4807
+ rect({ x: -w / 2, y: -h / 2, width: w, height: h, class: "lt-pid-equip" }),
4808
+ line({ x1: -w / 2, y1: 0, x2: w / 2, y2: 0, class: "lt-pid-tray-line" }),
4809
+ line({ x1: -w / 2 + 8, y1: -h / 2 + 8, x2: w / 2 - 8, y2: -8, class: "lt-pid-tray-line" }),
4810
+ line({ x1: -w / 2 + 8, y1: 8, x2: w / 2 - 8, y2: h / 2 - 8, class: "lt-pid-tray-line" }),
4811
+ text({ x: 0, y: h / 2 + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4812
+ ]);
4813
+ }
4814
+ case "cyclone": {
4815
+ const w = 70;
4816
+ const h = 100;
4817
+ const cyl = 30;
4818
+ return group({}, [
4819
+ path({
4820
+ d: `M ${-w / 2} ${-h / 2}
4821
+ L ${w / 2} ${-h / 2}
4822
+ L ${w / 2} ${-h / 2 + cyl}
4823
+ L 0 ${h / 2}
4824
+ L ${-w / 2} ${-h / 2 + cyl}
4825
+ Z`,
4826
+ class: "lt-pid-equip"
4827
+ }),
4828
+ text({ x: 0, y: h / 2 + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4829
+ ]);
4830
+ }
4831
+ case "flare": {
4832
+ const w = 30;
4833
+ const h = 110;
4834
+ return group({}, [
4835
+ rect({ x: -w / 2, y: -h / 2, width: w, height: h, class: "lt-pid-equip" }),
4836
+ polygon({
4837
+ points: `${ -8},${-h / 2 - 4} 0,${-h / 2 - 22} 8,${-h / 2 - 4}`,
4838
+ fill: "#ff7755",
4839
+ stroke: STROKE_BLACK
4840
+ }),
4841
+ text({ x: 0, y: h / 2 + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4842
+ ]);
4843
+ }
4844
+ case "cooling_tower": {
4845
+ const w = 100;
4846
+ const h = 90;
4847
+ return group({}, [
4848
+ path({
4849
+ d: `M ${-w / 2} ${-h / 2}
4850
+ L ${w / 2} ${-h / 2}
4851
+ L ${w / 4} 0
4852
+ L ${w / 2} ${h / 2}
4853
+ L ${-w / 2} ${h / 2}
4854
+ L ${-w / 4} 0
4855
+ Z`,
4856
+ class: "lt-pid-equip"
4857
+ }),
4858
+ text({ x: 0, y: h / 2 + 14, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4859
+ ]);
4860
+ }
4861
+ // ── Valves ──────────────────────────────────────────
4862
+ case "valve_gate":
4863
+ return group({}, [
4864
+ bowtie(),
4865
+ text({ x: 0, y: 22, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4866
+ ]);
4867
+ case "valve_ball":
4868
+ return group({}, [
4869
+ bowtie(),
4870
+ circle({ cx: 0, cy: 0, r: 4, fill: STROKE_BLACK }),
4871
+ text({ x: 0, y: 22, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4872
+ ]);
4873
+ case "valve_globe":
4874
+ return group({}, [
4875
+ bowtie(),
4876
+ circle({ cx: 0, cy: -5, r: 5, class: "lt-pid-valve-body", fill: FILL_WHITE }),
4877
+ text({ x: 0, y: 22, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4878
+ ]);
4879
+ case "valve_butterfly":
4880
+ return group({}, [
4881
+ bowtie(),
4882
+ line({ x1: 0, y1: -10, x2: 0, y2: 10, class: "lt-pid-tray-line" }),
4883
+ text({ x: 0, y: 22, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4884
+ ]);
4885
+ case "valve_check":
4886
+ return group({}, [
4887
+ bowtie(),
4888
+ path({ d: "M -10 -8 A 10 10 0 0 1 -10 8", class: "lt-pid-tray-line", fill: "none" }),
4889
+ text({ x: 0, y: 22, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4890
+ ]);
4891
+ case "valve_control":
4892
+ return group({}, [
4893
+ bowtie(),
4894
+ // actuator: diaphragm
4895
+ line({ x1: 0, y1: -11, x2: 0, y2: -22, class: "lt-pid-tray-line" }),
4896
+ path({ d: "M -14 -22 A 14 8 0 0 1 14 -22 L -14 -22 Z", class: "lt-pid-equip" }),
4897
+ text({ x: 0, y: 24, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4898
+ ]);
4899
+ case "valve_psv":
4900
+ return group({}, [
4901
+ bowtie(),
4902
+ // diagonal outlet (45°)
4903
+ line({
4904
+ x1: 18,
4905
+ y1: -11,
4906
+ x2: 26,
4907
+ y2: -22,
4908
+ class: "lt-pid-process"
4909
+ }),
4910
+ // spring stack
4911
+ path({
4912
+ d: "M -6 -11 L -10 -16 L -2 -20 L -10 -24 L -2 -28",
4913
+ class: "lt-pid-tray-line",
4914
+ fill: "none"
4915
+ }),
4916
+ text({ x: 0, y: 24, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4917
+ ]);
4918
+ default:
4919
+ return group({}, [
4920
+ rect({ x: -30, y: -20, width: 60, height: 40, class: "lt-pid-equip" }),
4921
+ text({ x: 0, y: 4, "text-anchor": "middle", class: "lt-pid-equip-tag" }, label)
4922
+ ]);
4923
+ }
4924
+ }
4925
+ function renderInstrument(category, letterCode, loopNumber) {
4926
+ const r = 14;
4927
+ const isComputer = category.endsWith("computer");
4928
+ const isPlc = category.endsWith("plc");
4929
+ const isShared = category.endsWith("shared");
4930
+ const isControlRoom = category.startsWith("cr_");
4931
+ const isLocal = category.startsWith("local_");
4932
+ const parts = [];
4933
+ if (isComputer) {
4934
+ parts.push(circle({ cx: 0, cy: 0, r, class: "lt-inst-body" }));
4935
+ parts.push(
4936
+ polygon({
4937
+ points: `0,${-r + 1} ${r - 1},0 0,${r - 1} ${ -13},0`,
4938
+ class: "lt-inst-body",
4939
+ fill: "none"
4940
+ })
4941
+ );
4942
+ } else if (isPlc) {
4943
+ parts.push(circle({ cx: 0, cy: 0, r, class: "lt-inst-body" }));
4944
+ const side = r * Math.SQRT1_2 * 2 - 2;
4945
+ parts.push(
4946
+ rect({
4947
+ x: -side / 2,
4948
+ y: -side / 2,
4949
+ width: side,
4950
+ height: side,
4951
+ class: "lt-inst-body",
4952
+ fill: "none"
4953
+ })
4954
+ );
4955
+ } else if (isShared) {
4956
+ parts.push(circle({ cx: 0, cy: 0, r, class: "lt-inst-body" }));
4957
+ const hex = [];
4958
+ for (let i = 0; i < 6; i++) {
4959
+ const a = Math.PI / 3 * i - Math.PI / 2;
4960
+ hex.push(`${(r - 2) * Math.cos(a)},${(r - 2) * Math.sin(a)}`);
4961
+ }
4962
+ parts.push(
4963
+ polygon({
4964
+ points: hex.join(" "),
4965
+ class: "lt-inst-body",
4966
+ fill: "none"
4967
+ })
4968
+ );
4969
+ } else {
4970
+ parts.push(circle({ cx: 0, cy: 0, r, class: "lt-inst-body" }));
4971
+ }
4972
+ if (isControlRoom) {
4973
+ parts.push(line({ x1: -r, y1: 0, x2: r, y2: 0, class: "lt-inst-cr-line" }));
4974
+ } else if (isLocal) {
4975
+ parts.push(line({ x1: -r, y1: 0, x2: r, y2: 0, class: "lt-inst-local-line" }));
4976
+ }
4977
+ parts.push(
4978
+ text(
4979
+ {
4980
+ x: 0,
4981
+ y: -3,
4982
+ "text-anchor": "middle",
4983
+ class: "lt-inst-tag"
4984
+ },
4985
+ letterCode
4986
+ )
4987
+ );
4988
+ parts.push(
4989
+ text(
4990
+ {
4991
+ x: 0,
4992
+ y: 9,
4993
+ "text-anchor": "middle",
4994
+ class: "lt-inst-tag"
4995
+ },
4996
+ loopNumber
4997
+ )
4998
+ );
4999
+ return group({ class: "lt-inst-symbol" }, parts);
5000
+ }
5001
+
5002
+ // src/diagrams/pid/layout.ts
5003
+ var PADDING = 30;
5004
+ var TITLE_AREA = 26;
5005
+ var EQUIP_GAP_X = 70;
5006
+ var INST_RADIUS = 14;
5007
+ var INST_OFFSET = 38;
5008
+ function defaultPort(direction, equip) {
5009
+ if (equip.equipType === "tank_atm" || equip.equipType === "tank_cone_roof") {
5010
+ return direction === "out" ? "bottom" : "top";
5011
+ }
5012
+ if (equip.equipType === "vessel_v" || equip.equipType === "column_tray" || equip.equipType === "column_packed") {
5013
+ return direction === "out" ? "bottom" : "top";
5014
+ }
5015
+ return direction === "out" ? "out" : "in";
5016
+ }
5017
+ function resolveSide(port) {
5018
+ if (port === "top" || port === "vapor_out" || port === "reflux") return "top";
5019
+ if (port === "bottom" || port === "liquid_out" || port === "bottom_return") return "bottom";
5020
+ if (port === "left" || port === "in" || port === "feed" || port === "tube_in" || port === "shell_in") return "left";
5021
+ return "right";
5022
+ }
5023
+ function getAnchor(layoutEq, port, fallback) {
5024
+ const ports = layoutEq.ports;
5025
+ let key = port;
5026
+ if (!key || !(key in ports)) {
5027
+ key = defaultPort(fallback, layoutEq.equip);
5028
+ }
5029
+ const p = ports[key] ?? ports.right ?? ports.out ?? ports.left ?? ports.in;
5030
+ if (!p) return { x: layoutEq.cx, y: layoutEq.cy, side: "right" };
5031
+ return { x: p.x, y: p.y, side: resolveSide(key ?? "right") };
5032
+ }
5033
+ function manhattanPath(fromX, fromY, fromSide, toX, toY, toSide) {
5034
+ const isHFrom = fromSide === "left" || fromSide === "right";
5035
+ const isHTo = toSide === "left" || toSide === "right";
5036
+ if (isHFrom && isHTo) {
5037
+ const midX = (fromX + toX) / 2;
5038
+ return {
5039
+ d: `M ${fromX} ${fromY} L ${midX} ${fromY} L ${midX} ${toY} L ${toX} ${toY}`,
5040
+ midX,
5041
+ midY: (fromY + toY) / 2
5042
+ };
5043
+ }
5044
+ if (!isHFrom && !isHTo) {
5045
+ const midY = (fromY + toY) / 2;
5046
+ return {
5047
+ d: `M ${fromX} ${fromY} L ${fromX} ${midY} L ${toX} ${midY} L ${toX} ${toY}`,
5048
+ midX: (fromX + toX) / 2,
5049
+ midY
5050
+ };
5051
+ }
5052
+ if (isHFrom) {
5053
+ return {
5054
+ d: `M ${fromX} ${fromY} L ${toX} ${fromY} L ${toX} ${toY}`,
5055
+ midX: toX,
5056
+ midY: (fromY + toY) / 2
5057
+ };
5058
+ }
5059
+ return {
5060
+ d: `M ${fromX} ${fromY} L ${fromX} ${toY} L ${toX} ${toY}`,
5061
+ midX: (fromX + toX) / 2,
5062
+ midY: toY
5063
+ };
5064
+ }
5065
+ function layoutPid(ast) {
5066
+ const equipment = [];
5067
+ const equipById = /* @__PURE__ */ new Map();
5068
+ const heights = ast.equipment.map((e) => GEOMETRY[e.equipType]?.height ?? 60);
5069
+ const maxH = Math.max(...heights, 0);
5070
+ const rowY = PADDING + TITLE_AREA + maxH / 2 + 30;
5071
+ let cursorX = PADDING + 40;
5072
+ for (const equip of ast.equipment) {
5073
+ const geo = GEOMETRY[equip.equipType] ?? { width: 60, height: 40, ports: {} };
5074
+ const cx = cursorX + geo.width / 2;
5075
+ const cy = rowY;
5076
+ const x = cx - geo.width / 2;
5077
+ const y = cy - geo.height / 2;
5078
+ const ports = {};
5079
+ for (const [name, p] of Object.entries(geo.ports)) {
5080
+ ports[name] = { x: cx + p.x, y: cy + p.y };
5081
+ }
5082
+ const layoutEq = {
5083
+ equip,
5084
+ x,
5085
+ y,
5086
+ width: geo.width,
5087
+ height: geo.height,
5088
+ cx,
5089
+ cy,
5090
+ ports
5091
+ };
5092
+ equipment.push(layoutEq);
5093
+ equipById.set(equip.id, layoutEq);
5094
+ cursorX += geo.width + EQUIP_GAP_X;
5095
+ }
5096
+ const instruments = [];
5097
+ const instById = /* @__PURE__ */ new Map();
5098
+ const crBandY = PADDING + TITLE_AREA + 40;
5099
+ let crSlot = 0;
5100
+ for (const inst of ast.instruments) {
5101
+ let cx = 0;
5102
+ let cy = 0;
5103
+ if (inst.category.startsWith("cr_")) {
5104
+ const tgt = inst.controls ?? inst.measures ?? "";
5105
+ const tgtEq = equipById.get(tgt);
5106
+ cx = tgtEq ? tgtEq.cx : PADDING + 80 + crSlot * (INST_RADIUS * 2 + 28);
5107
+ cy = crBandY;
5108
+ crSlot += 1;
5109
+ } else if (inst.category.startsWith("local_")) {
5110
+ cy = rowY + maxH / 2 + INST_OFFSET + INST_RADIUS;
5111
+ const tgt = inst.measures ?? inst.controls ?? "";
5112
+ const tgtEq = equipById.get(tgt);
5113
+ cx = tgtEq ? tgtEq.cx : PADDING + 80;
5114
+ } else {
5115
+ cy = rowY + maxH / 2 + INST_OFFSET;
5116
+ const tgt = inst.measures ?? inst.controls ?? "";
5117
+ const tgtEq = equipById.get(tgt);
5118
+ cx = tgtEq ? tgtEq.cx : PADDING + 80;
5119
+ }
5120
+ const lay = {
5121
+ inst,
5122
+ cx,
5123
+ cy,
5124
+ r: INST_RADIUS
5125
+ };
5126
+ instruments.push(lay);
5127
+ instById.set(inst.tag, lay);
5128
+ }
5129
+ const sameRow = (a, b) => Math.abs(a.cy - b.cy) < INST_RADIUS && Math.abs(a.cx - b.cx) < INST_RADIUS * 2 + 8;
5130
+ const sortedByX = [...instruments].sort((a, b) => a.cx - b.cx);
5131
+ for (let i = 1; i < sortedByX.length; i++) {
5132
+ const prev = sortedByX[i - 1];
5133
+ const cur = sortedByX[i];
5134
+ if (sameRow(prev, cur)) {
5135
+ cur.cx = prev.cx + INST_RADIUS * 2 + 14;
5136
+ }
5137
+ }
5138
+ const lines = [];
5139
+ for (const ln of ast.lines) {
5140
+ const path2 = routeLine(ln, equipById, instById);
5141
+ if (path2) lines.push(path2);
5142
+ }
5143
+ const allX = [];
5144
+ const allY = [];
5145
+ for (const e of equipment) {
5146
+ const tagPad = Math.max(0, ((e.equip.tag ?? e.equip.id).length * 6.6 - e.width) / 2 + 4);
5147
+ allX.push(e.x - tagPad, e.x + e.width + tagPad);
5148
+ allY.push(e.y, e.y + e.height + 30);
5149
+ }
5150
+ for (const i of instruments) {
5151
+ allX.push(i.cx - i.r, i.cx + i.r);
5152
+ allY.push(i.cy - i.r, i.cy + i.r + 14);
5153
+ }
5154
+ if (allX.length === 0) {
5155
+ allX.push(0, 400);
5156
+ allY.push(0, 200);
5157
+ }
5158
+ const maxX = Math.max(...allX);
5159
+ const maxY = Math.max(...allY);
5160
+ return {
5161
+ width: Math.max(maxX + PADDING, 400),
5162
+ height: Math.max(maxY + PADDING, 200),
5163
+ equipment,
5164
+ instruments,
5165
+ lines,
5166
+ title: ast.title
5167
+ };
5168
+ }
5169
+ function routeLine(ln, equipById, instById) {
5170
+ const fromAnchor = resolveAnchor(ln.from.id, ln.from.port, "out", equipById, instById);
5171
+ const toAnchor = resolveAnchor(ln.to.id, ln.to.port, "in", equipById, instById);
5172
+ if (!fromAnchor || !toAnchor) return void 0;
5173
+ const { d, midX, midY } = manhattanPath(
5174
+ fromAnchor.x,
5175
+ fromAnchor.y,
5176
+ fromAnchor.side,
5177
+ toAnchor.x,
5178
+ toAnchor.y,
5179
+ toAnchor.side
5180
+ );
5181
+ return {
5182
+ line: ln,
5183
+ path: d,
5184
+ midX,
5185
+ midY
5186
+ };
5187
+ }
5188
+ function resolveAnchor(id, port, fallback, equipById, instById) {
5189
+ const eq = equipById.get(id);
5190
+ if (eq) return getAnchor(eq, port, fallback);
5191
+ const inst = instById.get(id);
5192
+ if (inst) {
5193
+ return {
5194
+ x: inst.cx,
5195
+ y: inst.cy,
5196
+ side: fallback === "out" ? "right" : "left"
5197
+ };
5198
+ }
5199
+ return void 0;
5200
+ }
5201
+
5202
+ // src/diagrams/pid/renderer.ts
5203
+ var STYLE2 = `
5204
+ .lt-pid-equip { fill: #ffffff; stroke: #1d1d1d; stroke-width: 1.6; }
5205
+ .lt-pid-equip-tag { font: 600 11px system-ui, sans-serif; fill: #1d1d1d; }
5206
+ .lt-pid-tray-line { stroke: #555; stroke-width: 1; fill: none; }
5207
+
5208
+ .lt-pid-process { stroke: #1d1d1d; stroke-width: 2.6; fill: none; }
5209
+ .lt-pid-process-min { stroke: #1d1d1d; stroke-width: 1.5; fill: none; }
5210
+ .lt-pid-pneumatic { stroke: #1d1d1d; stroke-width: 1.4; fill: none; }
5211
+ .lt-pid-electric { stroke: #1d1d1d; stroke-width: 1.4; fill: none; stroke-dasharray: 6 4; }
5212
+ .lt-pid-hydraulic { stroke: #1d1d1d; stroke-width: 1.4; fill: none; stroke-dasharray: 10 4; }
5213
+ .lt-pid-capillary { stroke: #1d1d1d; stroke-width: 1.4; fill: none; stroke-dasharray: 1 5; stroke-linecap: round; }
5214
+ .lt-pid-software { stroke: #6a6a6a; stroke-width: 1.3; fill: none; stroke-dasharray: 2 4; }
5215
+ .lt-pid-mechanical { stroke: #1d1d1d; stroke-width: 1.4; fill: none; stroke-dasharray: 3 2 1 2; }
5216
+
5217
+ .lt-pid-pneumatic-tick { stroke: #1d1d1d; stroke-width: 1.2; }
5218
+
5219
+ .lt-inst-body { fill: #ffffff; stroke: #1d1d1d; stroke-width: 1.4; }
5220
+ .lt-inst-tag { font: 600 9.5px system-ui, sans-serif; fill: #1d1d1d; }
5221
+ .lt-inst-cr-line { stroke: #1d1d1d; stroke-width: 1; }
5222
+ .lt-inst-local-line { stroke: #1d1d1d; stroke-width: 1; stroke-dasharray: 2 2; }
5223
+ .lt-pid-valve-body { fill: #ffffff; stroke: #1d1d1d; stroke-width: 1.4; }
5224
+
5225
+ .lt-pid-line-tag-bg { fill: #ffffff; stroke: #1d1d1d; stroke-width: 0.6; }
5226
+ .lt-pid-line-tag-text { font: 9px ui-monospace, monospace; fill: #1d1d1d; }
5227
+
5228
+ .lt-pid-title { font: 600 14px system-ui, sans-serif; fill: #1d1d1d; }
5229
+ `;
5230
+ var ARROW_ID = "lt-pid-arrow";
5231
+ function lineClass(t) {
5232
+ return `lt-pid-${t.replace("_", "-")}`;
5233
+ }
5234
+ function renderLine(l) {
5235
+ const cls = lineClass(l.line.lineType);
5236
+ const parts = [
5237
+ path({
5238
+ d: l.path,
5239
+ class: cls,
5240
+ "data-line-id": l.line.id,
5241
+ "data-service": l.line.service ?? ""
5242
+ })
5243
+ ];
5244
+ if (l.line.lineType === "pneumatic") {
5245
+ parts.push(...pneumaticTicks(l.path));
5246
+ }
5247
+ if (l.line.tag) {
5248
+ const w = Math.max(28, l.line.tag.length * 6);
5249
+ parts.push(
5250
+ rect({
5251
+ x: l.midX - w / 2,
5252
+ y: l.midY - 8,
5253
+ width: w,
5254
+ height: 14,
5255
+ rx: 2,
5256
+ ry: 2,
5257
+ class: "lt-pid-line-tag-bg"
5258
+ })
5259
+ );
5260
+ parts.push(
5261
+ text(
5262
+ {
5263
+ x: l.midX,
5264
+ y: l.midY + 3,
5265
+ "text-anchor": "middle",
5266
+ class: "lt-pid-line-tag-text"
5267
+ },
5268
+ l.line.tag
5269
+ )
5270
+ );
5271
+ }
5272
+ return group({ class: "lt-pid-line", "data-id": l.line.id }, parts);
5273
+ }
5274
+ function pneumaticTicks(d) {
5275
+ const tokens = d.match(/([MLC])\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)/g) ?? [];
5276
+ const points = [];
5277
+ for (const t of tokens) {
5278
+ const m = t.match(/[MLC]\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)/);
5279
+ if (!m) continue;
5280
+ points.push({ x: parseFloat(m[1]), y: parseFloat(m[2]) });
5281
+ }
5282
+ const ticks = [];
5283
+ for (let i = 1; i < points.length; i++) {
5284
+ const a = points[i - 1];
5285
+ const b = points[i];
5286
+ const dx = b.x - a.x;
5287
+ const dy = b.y - a.y;
5288
+ const len = Math.hypot(dx, dy);
5289
+ if (len < 12) continue;
5290
+ const ux = dx / len;
5291
+ const uy = dy / len;
5292
+ const px = -uy;
5293
+ const py = ux;
5294
+ for (let s = 18; s < len; s += 26) {
5295
+ const cx = a.x + ux * s;
5296
+ const cy = a.y + uy * s;
5297
+ ticks.push(
5298
+ line({
5299
+ x1: cx + px * 4 - ux * 4,
5300
+ y1: cy + py * 4 - uy * 4,
5301
+ x2: cx - px * 4 + ux * 4,
5302
+ y2: cy - py * 4 + uy * 4,
5303
+ class: "lt-pid-pneumatic-tick"
5304
+ })
5305
+ );
5306
+ }
5307
+ }
5308
+ return ticks;
5309
+ }
5310
+ function renderPidAST(ast, _config) {
5311
+ const layout = layoutPid(ast);
5312
+ return renderLayout2(layout);
5313
+ }
5314
+ function renderLayout2(layout) {
5315
+ const equipNodes = layout.equipment.map((eq) => {
5316
+ const symbol = renderEquip(eq.equip.equipType, eq.equip.tag ?? eq.equip.id);
5317
+ return group(
5318
+ {
5319
+ class: "lt-pid-equip-wrap",
5320
+ "data-id": eq.equip.id,
5321
+ "data-type": eq.equip.equipType,
5322
+ transform: `translate(${eq.cx} ${eq.cy})`
5323
+ },
5324
+ [symbol]
5325
+ );
5326
+ });
5327
+ const instNodes = layout.instruments.map((i) => {
5328
+ const { letter, number } = parseTag(i.inst.tag);
5329
+ return group(
5330
+ {
5331
+ class: "lt-inst",
5332
+ "data-tag": i.inst.tag,
5333
+ "data-category": i.inst.category,
5334
+ transform: `translate(${i.cx} ${i.cy})`
5335
+ },
5336
+ [renderInstrument(i.inst.category, letter, number)]
5337
+ );
5338
+ });
5339
+ const autoSignals = [];
5340
+ for (const i of layout.instruments) {
5341
+ if (i.inst.measures) {
5342
+ const eq = layout.equipment.find((e) => e.equip.id === i.inst.measures);
5343
+ if (eq) {
5344
+ const ax = i.cx;
5345
+ const ay = i.cy;
5346
+ const bx = eq.cx;
5347
+ const by = eq.cy + eq.height / 2;
5348
+ autoSignals.push(
5349
+ path({
5350
+ d: `M ${ax} ${ay} L ${ax} ${by + 8} L ${bx} ${by + 8} L ${bx} ${by}`,
5351
+ class: "lt-pid-electric"
5352
+ })
5353
+ );
5354
+ }
5355
+ }
5356
+ if (i.inst.controls) {
5357
+ const eq = layout.equipment.find((e) => e.equip.id === i.inst.controls);
5358
+ if (eq) {
5359
+ const ax = i.cx;
5360
+ const ay = i.cy + i.r;
5361
+ const bx = eq.cx;
5362
+ const by = eq.y;
5363
+ autoSignals.push(
5364
+ path({
5365
+ d: `M ${ax} ${ay} L ${bx} ${ay} L ${bx} ${by}`,
5366
+ class: "lt-pid-pneumatic"
5367
+ })
5368
+ );
5369
+ }
5370
+ }
5371
+ }
5372
+ const titleNode = layout.title ? text({ x: 16, y: 22, class: "lt-pid-title" }, layout.title) : "";
5373
+ return svgRoot(
5374
+ {
5375
+ width: layout.width,
5376
+ height: layout.height,
5377
+ viewBox: `0 0 ${layout.width} ${layout.height}`,
5378
+ class: "lt-pid",
5379
+ "data-diagram-type": "pid"
5380
+ },
5381
+ [
5382
+ el("title", {}, escapeXml(`P&ID${layout.title ? " \u2014 " + layout.title : ""}`)),
5383
+ el("desc", {}, "ISA-5.1 / ISO 10628 P&ID rendered by Schematex"),
5384
+ defs([
5385
+ el(
5386
+ "marker",
5387
+ {
5388
+ id: ARROW_ID,
5389
+ markerWidth: 10,
5390
+ markerHeight: 10,
5391
+ refX: 9,
5392
+ refY: 3,
5393
+ orient: "auto",
5394
+ markerUnits: "strokeWidth"
5395
+ },
5396
+ [polygon({ points: "0,0 10,3 0,6", fill: "#1d1d1d" })]
5397
+ ),
5398
+ el("style", {}, STYLE2)
5399
+ ]),
5400
+ titleNode,
5401
+ group({ class: "lt-pid-equipment" }, equipNodes),
5402
+ group({ class: "lt-pid-lines" }, layout.lines.map(renderLine)),
5403
+ group({ class: "lt-pid-signals" }, autoSignals),
5404
+ group({ class: "lt-pid-instruments" }, instNodes)
5405
+ ]
5406
+ );
5407
+ }
5408
+ function parseTag(tag) {
5409
+ const idx = tag.indexOf("-");
5410
+ if (idx < 0) return { letter: tag, number: "" };
5411
+ return { letter: tag.slice(0, idx), number: tag.slice(idx + 1) };
5412
+ }
5413
+
5414
+ // src/diagrams/pid/index.ts
5415
+ var pid = {
5416
+ type: "pid",
5417
+ detect(text2) {
5418
+ return /^\s*pid\b/i.test(text2);
5419
+ },
5420
+ parse: parsePid,
5421
+ render(text2, config) {
5422
+ const ast = parsePid(text2);
5423
+ return renderPidAST(ast);
5424
+ }
5425
+ };
5426
+
2629
5427
  // src/diagrams/mindmap/inline.ts
2630
5428
  var RE_TASK = /^\[( |x|X)\]\s+(.*)$/;
2631
5429
  function tokenizeInline(raw) {
@@ -2901,7 +5699,7 @@ function parseMindmap(text2) {
2901
5699
  }
2902
5700
 
2903
5701
  // src/diagrams/mindmap/layout.ts
2904
- var PADDING = 40;
5702
+ var PADDING2 = 40;
2905
5703
  var SIBLING_GAP = 18;
2906
5704
  var MAIN_GAP = 44;
2907
5705
  var UNDERLINE_GAP = 4;
@@ -3048,13 +5846,13 @@ function normalize(nodes) {
3048
5846
  minY = Math.min(minY, n.y - lh / 2);
3049
5847
  maxY = Math.max(maxY, n.y + lh / 2);
3050
5848
  }
3051
- const dx = PADDING - minX;
3052
- const dy = PADDING - minY;
5849
+ const dx = PADDING2 - minX;
5850
+ const dy = PADDING2 - minY;
3053
5851
  for (const n of nodes) {
3054
5852
  n.x += dx;
3055
5853
  n.y += dy;
3056
5854
  }
3057
- return { width: maxX - minX + PADDING * 2, height: maxY - minY + PADDING * 2 };
5855
+ return { width: maxX - minX + PADDING2 * 2, height: maxY - minY + PADDING2 * 2 };
3058
5856
  }
3059
5857
  function underlineY(n) {
3060
5858
  return n.y + n.labelHeight / 2 - UNDERLINE_GAP / 2;
@@ -3182,7 +5980,7 @@ function paletteColor(theme, branchIndex) {
3182
5980
  if (branchIndex < 0) return theme.centralFill;
3183
5981
  return theme.branchPalette[branchIndex % theme.branchPalette.length];
3184
5982
  }
3185
- function renderLine(line2, leftX, cy, fontSize, fontFamily, fontWeight, theme) {
5983
+ function renderLine2(line2, leftX, cy, fontSize, fontFamily, fontWeight, theme) {
3186
5984
  const baselineY = cy + fontSize * 0.35;
3187
5985
  const tspans = [];
3188
5986
  const decorations = [];
@@ -3297,7 +6095,7 @@ function tspan(attrs, content) {
3297
6095
  }
3298
6096
  return `<tspan ${pairs.join(" ")}>${escapeXml(content)}</tspan>`;
3299
6097
  }
3300
- function renderNode(n, color, theme, fontFamily) {
6098
+ function renderNode2(n, color, theme, fontFamily) {
3301
6099
  const isRoot = n.node.depth === 0;
3302
6100
  const isMain = n.node.depth === 1;
3303
6101
  const fs = n.fontSize;
@@ -3312,7 +6110,7 @@ function renderNode(n, color, theme, fontFamily) {
3312
6110
  for (let i = 0; i < lineCount; i++) {
3313
6111
  const line2 = n.lines[i];
3314
6112
  const cy = topY + (i + 0.5) * lh;
3315
- const r = renderLine(line2, textLeft, cy, fs, fontFamily, weight, theme);
6113
+ const r = renderLine2(line2, textLeft, cy, fs, fontFamily, weight, theme);
3316
6114
  decorations.push(...r.decorations);
3317
6115
  children.push(r.textElement);
3318
6116
  }
@@ -3363,7 +6161,7 @@ function renderMindmapAST(ast, themeName = "default", fontFamily = "system-ui, -
3363
6161
  const nodeSvgs = [];
3364
6162
  for (const n of layout.nodes) {
3365
6163
  const color = n.node.depth === 0 ? theme.centralFill : paletteColor(theme, n.branchIndex);
3366
- nodeSvgs.push(renderNode(n, color, theme, fontFamily));
6164
+ nodeSvgs.push(renderNode2(n, color, theme, fontFamily));
3367
6165
  }
3368
6166
  const title2 = ast.title ?? tokensToPlainText(ast.root.tokens);
3369
6167
  return svgRoot(
@@ -4823,7 +7621,9 @@ var plugins = [
4823
7621
  matrix,
4824
7622
  orgchart,
4825
7623
  decisiontree,
4826
- timeline
7624
+ timeline,
7625
+ state,
7626
+ pid
4827
7627
  ];
4828
7628
  function detectPlugin(text2, config) {
4829
7629
  if (config?.type) {
@@ -4855,6 +7655,6 @@ function render(text2, config) {
4855
7655
  return plugin.render(text2, renderConfig);
4856
7656
  }
4857
7657
 
4858
- export { decisiontree, parse, render, timeline };
4859
- //# sourceMappingURL=chunk-VPKCW4PB.js.map
4860
- //# sourceMappingURL=chunk-VPKCW4PB.js.map
7658
+ export { decisiontree, parse, pid, render, state, timeline };
7659
+ //# sourceMappingURL=chunk-NNU4RGT3.js.map
7660
+ //# sourceMappingURL=chunk-NNU4RGT3.js.map