schematex 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +379 -0
  3. package/dist/chunk-2MQWZ2XY.cjs +453 -0
  4. package/dist/chunk-2MQWZ2XY.cjs.map +1 -0
  5. package/dist/chunk-2UKC6ZCY.cjs +1803 -0
  6. package/dist/chunk-2UKC6ZCY.cjs.map +1 -0
  7. package/dist/chunk-34X3ZJ6E.cjs +783 -0
  8. package/dist/chunk-34X3ZJ6E.cjs.map +1 -0
  9. package/dist/chunk-3FTUWAXK.cjs +1220 -0
  10. package/dist/chunk-3FTUWAXK.cjs.map +1 -0
  11. package/dist/chunk-3J7TFUOC.js +745 -0
  12. package/dist/chunk-3J7TFUOC.js.map +1 -0
  13. package/dist/chunk-47ZC6EMJ.js +1009 -0
  14. package/dist/chunk-47ZC6EMJ.js.map +1 -0
  15. package/dist/chunk-4DBRNOPA.cjs +750 -0
  16. package/dist/chunk-4DBRNOPA.cjs.map +1 -0
  17. package/dist/chunk-4G7ZIBHN.js +778 -0
  18. package/dist/chunk-4G7ZIBHN.js.map +1 -0
  19. package/dist/chunk-5C7DPDHQ.js +1321 -0
  20. package/dist/chunk-5C7DPDHQ.js.map +1 -0
  21. package/dist/chunk-ADOXGKAK.js +1251 -0
  22. package/dist/chunk-ADOXGKAK.js.map +1 -0
  23. package/dist/chunk-BE5HNDA5.cjs +874 -0
  24. package/dist/chunk-BE5HNDA5.cjs.map +1 -0
  25. package/dist/chunk-CZRM7LT7.js +889 -0
  26. package/dist/chunk-CZRM7LT7.js.map +1 -0
  27. package/dist/chunk-D4JTSPOL.js +1795 -0
  28. package/dist/chunk-D4JTSPOL.js.map +1 -0
  29. package/dist/chunk-DS47NTWZ.cjs +1034 -0
  30. package/dist/chunk-DS47NTWZ.cjs.map +1 -0
  31. package/dist/chunk-FDLZEKEB.js +449 -0
  32. package/dist/chunk-FDLZEKEB.js.map +1 -0
  33. package/dist/chunk-FGPTCDUT.cjs +1851 -0
  34. package/dist/chunk-FGPTCDUT.cjs.map +1 -0
  35. package/dist/chunk-HDKDQAEQ.cjs +86 -0
  36. package/dist/chunk-HDKDQAEQ.cjs.map +1 -0
  37. package/dist/chunk-IX554O5K.js +346 -0
  38. package/dist/chunk-IX554O5K.js.map +1 -0
  39. package/dist/chunk-KLJEK547.js +71 -0
  40. package/dist/chunk-KLJEK547.js.map +1 -0
  41. package/dist/chunk-LMFSHK45.js +1028 -0
  42. package/dist/chunk-LMFSHK45.js.map +1 -0
  43. package/dist/chunk-MDICUK6F.cjs +1258 -0
  44. package/dist/chunk-MDICUK6F.cjs.map +1 -0
  45. package/dist/chunk-N7KOXOMX.cjs +363 -0
  46. package/dist/chunk-N7KOXOMX.cjs.map +1 -0
  47. package/dist/chunk-NYCIK4SU.cjs +775 -0
  48. package/dist/chunk-NYCIK4SU.cjs.map +1 -0
  49. package/dist/chunk-PDPHRZZT.js +770 -0
  50. package/dist/chunk-PDPHRZZT.js.map +1 -0
  51. package/dist/chunk-ROFLJ74T.js +1212 -0
  52. package/dist/chunk-ROFLJ74T.js.map +1 -0
  53. package/dist/chunk-S6BK5DB6.cjs +845 -0
  54. package/dist/chunk-S6BK5DB6.cjs.map +1 -0
  55. package/dist/chunk-U4I37IBN.js +874 -0
  56. package/dist/chunk-U4I37IBN.js.map +1 -0
  57. package/dist/chunk-U5GGE6PJ.js +839 -0
  58. package/dist/chunk-U5GGE6PJ.js.map +1 -0
  59. package/dist/chunk-UHLYS3W5.cjs +1015 -0
  60. package/dist/chunk-UHLYS3W5.cjs.map +1 -0
  61. package/dist/chunk-URSKIHSY.cjs +881 -0
  62. package/dist/chunk-URSKIHSY.cjs.map +1 -0
  63. package/dist/chunk-V6WO7RK7.cjs +1056 -0
  64. package/dist/chunk-V6WO7RK7.cjs.map +1 -0
  65. package/dist/chunk-VFQCTXOX.js +869 -0
  66. package/dist/chunk-VFQCTXOX.js.map +1 -0
  67. package/dist/chunk-XQ52ICHU.cjs +895 -0
  68. package/dist/chunk-XQ52ICHU.cjs.map +1 -0
  69. package/dist/chunk-XX4BKS7Y.js +1051 -0
  70. package/dist/chunk-XX4BKS7Y.js.map +1 -0
  71. package/dist/chunk-XXU36667.js +1844 -0
  72. package/dist/chunk-XXU36667.js.map +1 -0
  73. package/dist/chunk-ZX7QKZK2.cjs +1326 -0
  74. package/dist/chunk-ZX7QKZK2.cjs.map +1 -0
  75. package/dist/diagrams/blockdiagram/index.cjs +25 -0
  76. package/dist/diagrams/blockdiagram/index.cjs.map +1 -0
  77. package/dist/diagrams/blockdiagram/index.d.cts +67 -0
  78. package/dist/diagrams/blockdiagram/index.d.ts +67 -0
  79. package/dist/diagrams/blockdiagram/index.js +4 -0
  80. package/dist/diagrams/blockdiagram/index.js.map +1 -0
  81. package/dist/diagrams/circuit/index.cjs +34 -0
  82. package/dist/diagrams/circuit/index.cjs.map +1 -0
  83. package/dist/diagrams/circuit/index.d.cts +138 -0
  84. package/dist/diagrams/circuit/index.d.ts +138 -0
  85. package/dist/diagrams/circuit/index.js +5 -0
  86. package/dist/diagrams/circuit/index.js.map +1 -0
  87. package/dist/diagrams/ecomap/index.cjs +30 -0
  88. package/dist/diagrams/ecomap/index.cjs.map +1 -0
  89. package/dist/diagrams/ecomap/index.d.cts +15 -0
  90. package/dist/diagrams/ecomap/index.d.ts +15 -0
  91. package/dist/diagrams/ecomap/index.js +5 -0
  92. package/dist/diagrams/ecomap/index.js.map +1 -0
  93. package/dist/diagrams/entity/index.cjs +26 -0
  94. package/dist/diagrams/entity/index.cjs.map +1 -0
  95. package/dist/diagrams/entity/index.d.cts +54 -0
  96. package/dist/diagrams/entity/index.d.ts +54 -0
  97. package/dist/diagrams/entity/index.js +5 -0
  98. package/dist/diagrams/entity/index.js.map +1 -0
  99. package/dist/diagrams/fishbone/index.cjs +34 -0
  100. package/dist/diagrams/fishbone/index.cjs.map +1 -0
  101. package/dist/diagrams/fishbone/index.d.cts +185 -0
  102. package/dist/diagrams/fishbone/index.d.ts +185 -0
  103. package/dist/diagrams/fishbone/index.js +5 -0
  104. package/dist/diagrams/fishbone/index.js.map +1 -0
  105. package/dist/diagrams/flowchart/index.cjs +34 -0
  106. package/dist/diagrams/flowchart/index.cjs.map +1 -0
  107. package/dist/diagrams/flowchart/index.d.cts +2 -0
  108. package/dist/diagrams/flowchart/index.d.ts +2 -0
  109. package/dist/diagrams/flowchart/index.js +5 -0
  110. package/dist/diagrams/flowchart/index.js.map +1 -0
  111. package/dist/diagrams/genogram/index.cjs +38 -0
  112. package/dist/diagrams/genogram/index.cjs.map +1 -0
  113. package/dist/diagrams/genogram/index.d.cts +20 -0
  114. package/dist/diagrams/genogram/index.d.ts +20 -0
  115. package/dist/diagrams/genogram/index.js +5 -0
  116. package/dist/diagrams/genogram/index.js.map +1 -0
  117. package/dist/diagrams/ladder/index.cjs +26 -0
  118. package/dist/diagrams/ladder/index.cjs.map +1 -0
  119. package/dist/diagrams/ladder/index.d.cts +49 -0
  120. package/dist/diagrams/ladder/index.d.ts +49 -0
  121. package/dist/diagrams/ladder/index.js +5 -0
  122. package/dist/diagrams/ladder/index.js.map +1 -0
  123. package/dist/diagrams/logic/index.cjs +26 -0
  124. package/dist/diagrams/logic/index.cjs.map +1 -0
  125. package/dist/diagrams/logic/index.d.cts +73 -0
  126. package/dist/diagrams/logic/index.d.ts +73 -0
  127. package/dist/diagrams/logic/index.js +5 -0
  128. package/dist/diagrams/logic/index.js.map +1 -0
  129. package/dist/diagrams/orgchart/index.cjs +30 -0
  130. package/dist/diagrams/orgchart/index.cjs.map +1 -0
  131. package/dist/diagrams/orgchart/index.d.cts +100 -0
  132. package/dist/diagrams/orgchart/index.d.ts +100 -0
  133. package/dist/diagrams/orgchart/index.js +5 -0
  134. package/dist/diagrams/orgchart/index.js.map +1 -0
  135. package/dist/diagrams/pedigree/index.cjs +30 -0
  136. package/dist/diagrams/pedigree/index.cjs.map +1 -0
  137. package/dist/diagrams/pedigree/index.d.cts +15 -0
  138. package/dist/diagrams/pedigree/index.d.ts +15 -0
  139. package/dist/diagrams/pedigree/index.js +5 -0
  140. package/dist/diagrams/pedigree/index.js.map +1 -0
  141. package/dist/diagrams/phylo/index.cjs +30 -0
  142. package/dist/diagrams/phylo/index.cjs.map +1 -0
  143. package/dist/diagrams/phylo/index.d.cts +32 -0
  144. package/dist/diagrams/phylo/index.d.ts +32 -0
  145. package/dist/diagrams/phylo/index.js +5 -0
  146. package/dist/diagrams/phylo/index.js.map +1 -0
  147. package/dist/diagrams/sld/index.cjs +26 -0
  148. package/dist/diagrams/sld/index.cjs.map +1 -0
  149. package/dist/diagrams/sld/index.d.cts +58 -0
  150. package/dist/diagrams/sld/index.d.ts +58 -0
  151. package/dist/diagrams/sld/index.js +5 -0
  152. package/dist/diagrams/sld/index.js.map +1 -0
  153. package/dist/diagrams/sociogram/index.cjs +26 -0
  154. package/dist/diagrams/sociogram/index.cjs.map +1 -0
  155. package/dist/diagrams/sociogram/index.d.cts +76 -0
  156. package/dist/diagrams/sociogram/index.d.ts +76 -0
  157. package/dist/diagrams/sociogram/index.js +5 -0
  158. package/dist/diagrams/sociogram/index.js.map +1 -0
  159. package/dist/diagrams/timing/index.cjs +21 -0
  160. package/dist/diagrams/timing/index.cjs.map +1 -0
  161. package/dist/diagrams/timing/index.d.cts +9 -0
  162. package/dist/diagrams/timing/index.d.ts +9 -0
  163. package/dist/diagrams/timing/index.js +4 -0
  164. package/dist/diagrams/timing/index.js.map +1 -0
  165. package/dist/diagrams/venn/index.cjs +38 -0
  166. package/dist/diagrams/venn/index.cjs.map +1 -0
  167. package/dist/diagrams/venn/index.d.cts +69 -0
  168. package/dist/diagrams/venn/index.d.ts +69 -0
  169. package/dist/diagrams/venn/index.js +5 -0
  170. package/dist/diagrams/venn/index.js.map +1 -0
  171. package/dist/index-BSlza1YY.d.ts +150 -0
  172. package/dist/index-BXefHVce.d.cts +150 -0
  173. package/dist/index.cjs +2033 -0
  174. package/dist/index.cjs.map +1 -0
  175. package/dist/index.d.cts +29 -0
  176. package/dist/index.d.ts +29 -0
  177. package/dist/index.js +1944 -0
  178. package/dist/index.js.map +1 -0
  179. package/dist/types-DqfcYkcY.d.cts +741 -0
  180. package/dist/types-DqfcYkcY.d.ts +741 -0
  181. package/package.json +163 -0
@@ -0,0 +1,1803 @@
1
+ 'use strict';
2
+
3
+ var chunkN7KOXOMX_cjs = require('./chunk-N7KOXOMX.cjs');
4
+ var chunkHDKDQAEQ_cjs = require('./chunk-HDKDQAEQ.cjs');
5
+
6
+ // src/diagrams/genogram/parser.ts
7
+ var ParseError = class extends Error {
8
+ constructor(message, line2, column, source) {
9
+ super(`Line ${line2}, col ${column}: ${message}
10
+ \u2192 ${source}`);
11
+ this.line = line2;
12
+ this.column = column;
13
+ this.source = source;
14
+ this.name = "ParseError";
15
+ }
16
+ line;
17
+ column;
18
+ source;
19
+ };
20
+ var COUPLE_OPS = [
21
+ { token: "-x-", type: "divorced" },
22
+ { token: "-/-", type: "separated" },
23
+ { token: "-o-", type: "engaged" },
24
+ { token: "==", type: "consanguineous" },
25
+ { token: "--", type: "married" },
26
+ { token: "~", type: "cohabiting" }
27
+ ];
28
+ var VALID_SEX = /* @__PURE__ */ new Set(["male", "female", "unknown", "other"]);
29
+ var VALID_STATUS = /* @__PURE__ */ new Set([
30
+ "deceased",
31
+ "stillborn",
32
+ "miscarriage",
33
+ "abortion"
34
+ ]);
35
+ var SPECIAL_CHILD_PROPS = /* @__PURE__ */ new Set([
36
+ "adopted",
37
+ "foster",
38
+ "twin-identical",
39
+ "twin-fraternal"
40
+ ]);
41
+ var VALID_FILLS = /* @__PURE__ */ new Set([
42
+ "full",
43
+ "half-left",
44
+ "half-right",
45
+ "half-top",
46
+ "half-bottom",
47
+ "quad-tl",
48
+ "quad-tr",
49
+ "quad-bl",
50
+ "quad-br",
51
+ "quarter",
52
+ "striped",
53
+ "dotted"
54
+ ]);
55
+ var EMOTIONAL_TYPES = /* @__PURE__ */ new Set([
56
+ "harmony",
57
+ "close",
58
+ "bestfriends",
59
+ "love",
60
+ "inlove",
61
+ "friendship",
62
+ "hostile",
63
+ "conflict",
64
+ "enmity",
65
+ "distant-hostile",
66
+ "cutoff",
67
+ "close-hostile",
68
+ "fused",
69
+ "fused-hostile",
70
+ "distant",
71
+ "normal",
72
+ "nevermet",
73
+ "abuse",
74
+ "physical-abuse",
75
+ "emotional-abuse",
76
+ "sexual-abuse",
77
+ "neglect",
78
+ "manipulative",
79
+ "controlling",
80
+ "jealous",
81
+ "focused",
82
+ "focused-neg",
83
+ "distrust",
84
+ "admirer",
85
+ "limerence"
86
+ ]);
87
+ function parseGenogram(text2) {
88
+ const rawLines = text2.split("\n");
89
+ const state = { lines: rawLines, currentLine: 0 };
90
+ skipBlankAndComments(state);
91
+ const metadata = {};
92
+ const headerLine = currentLineText(state);
93
+ if (headerLine === void 0) {
94
+ throw new ParseError("Empty input", 1, 1, "");
95
+ }
96
+ const headerTrimmed = headerLine.trim();
97
+ if (!headerTrimmed.toLowerCase().startsWith("genogram")) {
98
+ throw new ParseError(
99
+ 'Expected "genogram" header',
100
+ state.currentLine + 1,
101
+ 1,
102
+ headerLine
103
+ );
104
+ }
105
+ const titleMatch = headerTrimmed.match(/^genogram\s+"([^"]*)"$/i);
106
+ if (titleMatch) {
107
+ metadata.title = titleMatch[1];
108
+ }
109
+ state.currentLine++;
110
+ const individualsMap = /* @__PURE__ */ new Map();
111
+ const relationships = [];
112
+ const childSpecialProps = /* @__PURE__ */ new Map();
113
+ skipBlankAndComments(state);
114
+ while (state.currentLine < state.lines.length) {
115
+ skipBlankAndComments(state);
116
+ if (state.currentLine >= state.lines.length) break;
117
+ const lineText = state.lines[state.currentLine];
118
+ const trimmed = lineText.trim();
119
+ if (trimmed === "" || trimmed.startsWith("#")) {
120
+ state.currentLine++;
121
+ continue;
122
+ }
123
+ const emotionalMatch = detectEmotionalOp(trimmed);
124
+ if (emotionalMatch) {
125
+ const { leftId, emotionalType, rightId: emRightId, directional, label: emLabel } = emotionalMatch;
126
+ const lineNum = state.currentLine + 1;
127
+ const leftKey = leftId.toLowerCase();
128
+ const rightKey = emRightId.toLowerCase();
129
+ if (!individualsMap.has(leftKey)) {
130
+ throw new ParseError(`Unknown individual '${leftId}'`, lineNum, 1, lineText);
131
+ }
132
+ if (!individualsMap.has(rightKey)) {
133
+ throw new ParseError(`Unknown individual '${emRightId}'`, lineNum, 1, lineText);
134
+ }
135
+ const rel = {
136
+ type: emotionalType,
137
+ from: leftKey,
138
+ to: rightKey
139
+ };
140
+ if (directional) rel.directional = true;
141
+ if (emLabel) rel.label = emLabel;
142
+ relationships.push(rel);
143
+ state.currentLine++;
144
+ continue;
145
+ }
146
+ const coupleMatch = detectCoupleOp(trimmed);
147
+ if (coupleMatch) {
148
+ const { leftId, op, rightRaw } = coupleMatch;
149
+ const lineNum = state.currentLine + 1;
150
+ const { cleaned: rightCleaned, label: relLabel } = extractRelLabel(rightRaw);
151
+ const { id: rightId, props: rightProps } = parseIdWithOptionalProps(rightCleaned);
152
+ const leftKey = leftId.toLowerCase();
153
+ if (!individualsMap.has(leftKey)) {
154
+ throw new ParseError(
155
+ `Unknown individual '${leftId}'`,
156
+ lineNum,
157
+ 1,
158
+ lineText
159
+ );
160
+ }
161
+ const rightKey = rightId.toLowerCase();
162
+ if (rightProps) {
163
+ const rightIndividual = buildIndividual(rightId, rightProps, lineNum, lineText);
164
+ individualsMap.set(rightKey, rightIndividual);
165
+ } else if (!individualsMap.has(rightKey)) {
166
+ throw new ParseError(
167
+ `Unknown individual '${rightId}'`,
168
+ lineNum,
169
+ 1,
170
+ lineText
171
+ );
172
+ }
173
+ const rel = { type: op.type, from: leftKey, to: rightKey };
174
+ if (relLabel) rel.label = relLabel;
175
+ relationships.push(rel);
176
+ const coupleIndent = getIndent(lineText);
177
+ state.currentLine++;
178
+ while (state.currentLine < state.lines.length) {
179
+ const childLine = state.lines[state.currentLine];
180
+ const childTrimmed = childLine.trim();
181
+ if (childTrimmed === "" || childTrimmed.startsWith("#")) {
182
+ state.currentLine++;
183
+ continue;
184
+ }
185
+ const childIndent = getIndent(childLine);
186
+ if (childIndent <= coupleIndent) break;
187
+ const childLineNum = state.currentLine + 1;
188
+ const { id: childId, propsStr } = splitIdAndProps(childTrimmed);
189
+ const childKey = childId.toLowerCase();
190
+ const individual = buildIndividual(
191
+ childId,
192
+ propsStr,
193
+ childLineNum,
194
+ childLine
195
+ );
196
+ if (propsStr) {
197
+ for (const sp of SPECIAL_CHILD_PROPS) {
198
+ if (propsTokens(propsStr).includes(sp)) {
199
+ childSpecialProps.set(childKey, sp);
200
+ }
201
+ }
202
+ }
203
+ individualsMap.set(childKey, individual);
204
+ const coupleKey = `${leftKey}+${rightKey}`;
205
+ const pcType = childSpecialProps.get(childKey);
206
+ const relType = pcType && (pcType === "adopted" || pcType === "foster") ? pcType : "parent-child";
207
+ relationships.push({ type: relType, from: coupleKey, to: childKey });
208
+ state.currentLine++;
209
+ }
210
+ } else {
211
+ const lineNum = state.currentLine + 1;
212
+ const { id, propsStr } = splitIdAndProps(trimmed);
213
+ const key = id.toLowerCase();
214
+ const individual = buildIndividual(id, propsStr, lineNum, lineText);
215
+ const existing = individualsMap.get(key);
216
+ if (existing) {
217
+ individualsMap.set(key, mergeIndividual(existing, individual));
218
+ } else {
219
+ individualsMap.set(key, individual);
220
+ }
221
+ state.currentLine++;
222
+ }
223
+ }
224
+ const twinGroups = /* @__PURE__ */ new Map();
225
+ for (const [childKey, prop] of childSpecialProps) {
226
+ if (prop === "twin-identical" || prop === "twin-fraternal") {
227
+ const pcRel = relationships.find(
228
+ (r) => r.to === childKey && (r.type === "parent-child" || r.type === "adopted" || r.type === "foster")
229
+ );
230
+ if (pcRel) {
231
+ const groupKey = `${pcRel.from}:${prop}`;
232
+ const group2 = twinGroups.get(groupKey) ?? [];
233
+ group2.push(childKey);
234
+ twinGroups.set(groupKey, group2);
235
+ }
236
+ }
237
+ }
238
+ for (const [groupKey, members] of twinGroups) {
239
+ const twinType = groupKey.split(":")[1];
240
+ for (let i = 0; i < members.length - 1; i++) {
241
+ relationships.push({
242
+ type: twinType,
243
+ from: members[i],
244
+ to: members[i + 1]
245
+ });
246
+ }
247
+ }
248
+ return {
249
+ type: "genogram",
250
+ individuals: Array.from(individualsMap.values()),
251
+ relationships,
252
+ metadata: Object.keys(metadata).length > 0 ? metadata : void 0
253
+ };
254
+ }
255
+ function currentLineText(state) {
256
+ return state.lines[state.currentLine];
257
+ }
258
+ function skipBlankAndComments(state) {
259
+ while (state.currentLine < state.lines.length) {
260
+ const t = state.lines[state.currentLine].trim();
261
+ if (t === "" || t.startsWith("#")) {
262
+ state.currentLine++;
263
+ } else {
264
+ break;
265
+ }
266
+ }
267
+ }
268
+ function getIndent(line2) {
269
+ const match = line2.match(/^(\s*)/);
270
+ return match ? match[1].length : 0;
271
+ }
272
+ function detectEmotionalOp(trimmed) {
273
+ const match = trimmed.match(
274
+ /^([a-zA-Z][a-zA-Z0-9_-]*)\s+-([\w-]+)->(.*)|^([a-zA-Z][a-zA-Z0-9_-]*)\s+-([\w-]+)-\s+(.*)/
275
+ );
276
+ if (!match) return null;
277
+ const directional = !!match[1];
278
+ const leftId = directional ? match[1] : match[4];
279
+ const emotionalType = directional ? match[2] : match[5];
280
+ const rest = (directional ? match[3] : match[6]).trim();
281
+ if (!EMOTIONAL_TYPES.has(emotionalType)) return null;
282
+ const { id: rightId, label } = extractIdAndLabel(rest);
283
+ if (!rightId || !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(rightId)) return null;
284
+ return { leftId, emotionalType, rightId, directional, label };
285
+ }
286
+ function extractIdAndLabel(raw) {
287
+ const labelMatch = raw.match(/^([a-zA-Z][a-zA-Z0-9_-]*)\s+"([^"]*)"$/);
288
+ if (labelMatch) return { id: labelMatch[1], label: labelMatch[2] };
289
+ const idOnly = raw.match(/^([a-zA-Z][a-zA-Z0-9_-]*)$/);
290
+ if (idOnly) return { id: idOnly[1], label: null };
291
+ return { id: raw.trim(), label: null };
292
+ }
293
+ function extractRelLabel(rightRaw) {
294
+ const match = rightRaw.match(/^(.*?)\s+"([^"]*)"$/);
295
+ if (match) return { cleaned: match[1].trim(), label: match[2] };
296
+ return { cleaned: rightRaw, label: null };
297
+ }
298
+ function detectCoupleOp(trimmed) {
299
+ for (const op of COUPLE_OPS) {
300
+ const parts = splitByOperator(trimmed, op.token);
301
+ if (parts) {
302
+ return { leftId: parts.left.trim(), op, rightRaw: parts.right.trim() };
303
+ }
304
+ }
305
+ return null;
306
+ }
307
+ function splitByOperator(line2, op) {
308
+ let bracketDepth = 0;
309
+ for (let i = 0; i < line2.length; i++) {
310
+ if (line2[i] === "[") bracketDepth++;
311
+ if (line2[i] === "]") bracketDepth--;
312
+ if (bracketDepth > 0) continue;
313
+ if (line2.substring(i, i + op.length) === op) {
314
+ const left = line2.substring(0, i).trim();
315
+ const right = line2.substring(i + op.length).trim();
316
+ if (left && right) {
317
+ const leftId = left.match(/^[a-zA-Z][a-zA-Z0-9_-]*$/);
318
+ if (leftId) return { left, right };
319
+ }
320
+ }
321
+ }
322
+ return null;
323
+ }
324
+ function parseIdWithOptionalProps(raw) {
325
+ const bracketIdx = raw.indexOf("[");
326
+ if (bracketIdx === -1) {
327
+ return { id: raw.trim(), props: null };
328
+ }
329
+ const id = raw.substring(0, bracketIdx).trim();
330
+ const endBracket = raw.lastIndexOf("]");
331
+ const propsStr = raw.substring(bracketIdx + 1, endBracket === -1 ? raw.length : endBracket);
332
+ return { id, props: propsStr };
333
+ }
334
+ function splitIdAndProps(trimmed) {
335
+ const bracketIdx = trimmed.indexOf("[");
336
+ if (bracketIdx === -1) {
337
+ return { id: trimmed.trim(), propsStr: null };
338
+ }
339
+ const id = trimmed.substring(0, bracketIdx).trim();
340
+ const endBracket = trimmed.lastIndexOf("]");
341
+ const propsStr = trimmed.substring(
342
+ bracketIdx + 1,
343
+ endBracket === -1 ? trimmed.length : endBracket
344
+ );
345
+ return { id, propsStr };
346
+ }
347
+ function propsTokens(propsStr) {
348
+ return splitProps(propsStr).map((t) => t.trim().toLowerCase());
349
+ }
350
+ function splitProps(propsStr) {
351
+ const result = [];
352
+ let current = "";
353
+ let parenDepth = 0;
354
+ for (const ch of propsStr) {
355
+ if (ch === "(") parenDepth++;
356
+ if (ch === ")") parenDepth--;
357
+ if (ch === "," && parenDepth === 0) {
358
+ result.push(current);
359
+ current = "";
360
+ } else {
361
+ current += ch;
362
+ }
363
+ }
364
+ if (current.trim()) result.push(current);
365
+ return result;
366
+ }
367
+ function buildIndividual(id, propsStr, lineNum, lineText) {
368
+ const individual = {
369
+ id: id.toLowerCase(),
370
+ label: id,
371
+ sex: "unknown",
372
+ status: "alive"
373
+ };
374
+ if (!propsStr) return individual;
375
+ const tokens = splitProps(propsStr);
376
+ for (const rawToken of tokens) {
377
+ const token = rawToken.trim();
378
+ const tokenLower = token.toLowerCase();
379
+ if (VALID_SEX.has(tokenLower)) {
380
+ individual.sex = tokenLower;
381
+ } else if (VALID_STATUS.has(tokenLower)) {
382
+ individual.status = tokenLower;
383
+ } else if (tokenLower === "index") {
384
+ if (!individual.markers) individual.markers = [];
385
+ individual.markers.push("index-person");
386
+ } else if (SPECIAL_CHILD_PROPS.has(tokenLower)) {
387
+ continue;
388
+ } else if (/^\d{4}$/.test(tokenLower)) {
389
+ if (individual.birthYear !== void 0) {
390
+ individual.deathYear = parseInt(token, 10);
391
+ } else {
392
+ individual.birthYear = parseInt(token, 10);
393
+ }
394
+ } else if (tokenLower.startsWith("conditions:")) {
395
+ individual.conditions = parseConditions(
396
+ token.substring("conditions:".length).trim(),
397
+ lineNum,
398
+ lineText
399
+ );
400
+ } else if (token.includes(":")) {
401
+ const colonIdx = token.indexOf(":");
402
+ const key = token.substring(0, colonIdx).trim().toLowerCase();
403
+ const value = token.substring(colonIdx + 1).trim();
404
+ if (key === "age") {
405
+ const ageNum = parseInt(value, 10);
406
+ if (!isNaN(ageNum)) individual.age = ageNum;
407
+ } else if (key === "death") {
408
+ const deathNum = parseInt(value, 10);
409
+ if (!isNaN(deathNum)) individual.deathYear = deathNum;
410
+ } else if (key === "label") {
411
+ individual.label = value.replace(/^"|"$/g, "");
412
+ } else {
413
+ if (!individual.properties) individual.properties = {};
414
+ individual.properties[key] = value;
415
+ }
416
+ } else {
417
+ throw new ParseError(
418
+ `Unknown property '${token}'. Valid: male, female, unknown, deceased, stillborn, miscarriage, abortion, adopted, foster, twin-identical, twin-fraternal, index, a 4-digit year, conditions:..., age:N, death:YYYY, or key:value`,
419
+ lineNum,
420
+ 1,
421
+ lineText
422
+ );
423
+ }
424
+ }
425
+ return individual;
426
+ }
427
+ function parseConditions(raw, lineNum, lineText) {
428
+ const parts = raw.split("+").map((s) => s.trim());
429
+ const conditions = [];
430
+ for (const part of parts) {
431
+ const match = part.match(/^([a-zA-Z0-9_-]+)\(([^)]+)\)$/);
432
+ if (!match) {
433
+ throw new ParseError(
434
+ `Invalid condition format '${part}'. Expected: name(fill) or name(fill, #color)`,
435
+ lineNum,
436
+ 1,
437
+ lineText
438
+ );
439
+ }
440
+ const [, label, innerRaw] = match;
441
+ const innerParts = innerRaw.split(",").map((s) => s.trim());
442
+ const fill = innerParts[0];
443
+ const color = innerParts[1];
444
+ if (!VALID_FILLS.has(fill)) {
445
+ throw new ParseError(
446
+ `Invalid fill pattern '${fill}'. Valid: full, half-left, half-right, half-top, half-bottom, quad-tl, quad-tr, quad-bl, quad-br, quarter, striped, dotted`,
447
+ lineNum,
448
+ 1,
449
+ lineText
450
+ );
451
+ }
452
+ const cond = { label, fill };
453
+ if (color) cond.color = color;
454
+ conditions.push(cond);
455
+ }
456
+ return conditions;
457
+ }
458
+ function mergeIndividual(existing, incoming) {
459
+ return {
460
+ ...existing,
461
+ sex: incoming.sex !== "unknown" ? incoming.sex : existing.sex,
462
+ status: incoming.status !== "alive" ? incoming.status : existing.status,
463
+ birthYear: incoming.birthYear ?? existing.birthYear,
464
+ deathYear: incoming.deathYear ?? existing.deathYear,
465
+ conditions: incoming.conditions ?? existing.conditions,
466
+ properties: {
467
+ ...existing.properties,
468
+ ...incoming.properties
469
+ }
470
+ };
471
+ }
472
+
473
+ // src/diagrams/genogram/layout.ts
474
+ function layoutGenogram(ast, config) {
475
+ const graph = buildGraph(ast);
476
+ assignGenerations(graph);
477
+ const ordered = orderNodesInGenerations(graph);
478
+ const positions = assignPositions(ordered, graph, config);
479
+ const edges = computeEdges(graph, positions, config);
480
+ const emotionalEdges = computeEmotionalEdges(ast.relationships, positions, config);
481
+ return packageResult(positions, [...edges, ...emotionalEdges], graph, config);
482
+ }
483
+ function buildGraph(ast) {
484
+ const individuals = /* @__PURE__ */ new Map();
485
+ for (const ind of ast.individuals) {
486
+ individuals.set(ind.id, ind);
487
+ }
488
+ const familyUnits = [];
489
+ const childOf = /* @__PURE__ */ new Map();
490
+ const coupleRels = ast.relationships.filter(
491
+ (r) => r.type === "married" || r.type === "divorced" || r.type === "separated" || r.type === "engaged" || r.type === "cohabiting" || r.type === "consanguineous"
492
+ );
493
+ for (const rel of coupleRels) {
494
+ const fuId = `${rel.from}+${rel.to}`;
495
+ const indA = individuals.get(rel.from);
496
+ const indB = individuals.get(rel.to);
497
+ let partners;
498
+ if (indA && indB) {
499
+ if (indA.sex === "male" && indB.sex === "female") {
500
+ partners = [rel.from, rel.to];
501
+ } else if (indA.sex === "female" && indB.sex === "male") {
502
+ partners = [rel.to, rel.from];
503
+ } else {
504
+ const yearA = indA.birthYear ?? 9999;
505
+ const yearB = indB.birthYear ?? 9999;
506
+ partners = yearA <= yearB ? [rel.from, rel.to] : [rel.to, rel.from];
507
+ }
508
+ } else {
509
+ partners = [rel.from, rel.to];
510
+ }
511
+ const children = [];
512
+ for (const r of ast.relationships) {
513
+ if ((r.type === "parent-child" || r.type === "adopted" || r.type === "foster") && r.from === fuId) {
514
+ children.push(r.to);
515
+ childOf.set(r.to, fuId);
516
+ }
517
+ }
518
+ children.sort((a, b) => {
519
+ const indChildA = individuals.get(a);
520
+ const indChildB = individuals.get(b);
521
+ return (indChildA?.birthYear ?? 9999) - (indChildB?.birthYear ?? 9999);
522
+ });
523
+ familyUnits.push({
524
+ id: fuId,
525
+ partners,
526
+ relationship: rel.type,
527
+ label: rel.label,
528
+ children
529
+ });
530
+ }
531
+ return { individuals, familyUnits, generations: /* @__PURE__ */ new Map(), childOf };
532
+ }
533
+ function assignGenerations(graph) {
534
+ const { individuals, familyUnits, childOf, generations } = graph;
535
+ const allIds = Array.from(individuals.keys());
536
+ const roots = allIds.filter((id) => !childOf.has(id));
537
+ if (roots.length === 0 && allIds.length > 0) {
538
+ for (const id of allIds) generations.set(id, 0);
539
+ return;
540
+ }
541
+ for (const root of roots) {
542
+ if (!generations.has(root)) {
543
+ generations.set(root, 0);
544
+ }
545
+ }
546
+ let changed = true;
547
+ while (changed) {
548
+ changed = false;
549
+ for (const fu of familyUnits) {
550
+ const gen0 = generations.get(fu.partners[0]);
551
+ const gen1 = generations.get(fu.partners[1]);
552
+ let partnerGen;
553
+ if (gen0 !== void 0 && gen1 !== void 0) {
554
+ partnerGen = Math.max(gen0, gen1);
555
+ if (gen0 !== partnerGen) {
556
+ generations.set(fu.partners[0], partnerGen);
557
+ changed = true;
558
+ }
559
+ if (gen1 !== partnerGen) {
560
+ generations.set(fu.partners[1], partnerGen);
561
+ changed = true;
562
+ }
563
+ } else if (gen0 !== void 0) {
564
+ partnerGen = gen0;
565
+ generations.set(fu.partners[1], partnerGen);
566
+ changed = true;
567
+ } else if (gen1 !== void 0) {
568
+ partnerGen = gen1;
569
+ generations.set(fu.partners[0], partnerGen);
570
+ changed = true;
571
+ } else {
572
+ continue;
573
+ }
574
+ for (const childId of fu.children) {
575
+ const childGen = partnerGen + 1;
576
+ const existing = generations.get(childId);
577
+ if (existing === void 0 || existing < childGen) {
578
+ generations.set(childId, childGen);
579
+ changed = true;
580
+ }
581
+ }
582
+ }
583
+ }
584
+ for (const id of allIds) {
585
+ if (!generations.has(id)) {
586
+ generations.set(id, 0);
587
+ }
588
+ }
589
+ }
590
+ function orderNodesInGenerations(graph, _config) {
591
+ const { generations, familyUnits } = graph;
592
+ const genGroups = /* @__PURE__ */ new Map();
593
+ for (const [id, gen] of generations) {
594
+ const group2 = genGroups.get(gen) ?? [];
595
+ group2.push(id);
596
+ genGroups.set(gen, group2);
597
+ }
598
+ const genIndices = Array.from(genGroups.keys()).sort((a, b) => a - b);
599
+ const result = [];
600
+ for (const genIdx of genIndices) {
601
+ const nodeIds = genGroups.get(genIdx) ?? [];
602
+ const ordered = orderGeneration(nodeIds, genIdx, graph, familyUnits);
603
+ result.push({ index: genIdx, nodeIds: ordered });
604
+ }
605
+ return result;
606
+ }
607
+ function orderGeneration(nodeIds, _genIdx, graph, familyUnits) {
608
+ if (nodeIds.length <= 1) return [...nodeIds];
609
+ const nodeSet = new Set(nodeIds);
610
+ const placed = /* @__PURE__ */ new Set();
611
+ const ordered = [];
612
+ const fuInGen = familyUnits.filter(
613
+ (fu) => nodeSet.has(fu.partners[0]) || nodeSet.has(fu.partners[1])
614
+ );
615
+ const personToFUs = /* @__PURE__ */ new Map();
616
+ for (const fu of fuInGen) {
617
+ for (const p of fu.partners) {
618
+ if (nodeSet.has(p)) {
619
+ const arr = personToFUs.get(p) ?? [];
620
+ arr.push(fu);
621
+ personToFUs.set(p, arr);
622
+ }
623
+ }
624
+ }
625
+ function placePersonAndExpand(id) {
626
+ if (placed.has(id)) return;
627
+ ordered.push(id);
628
+ placed.add(id);
629
+ const fus = personToFUs.get(id) ?? [];
630
+ const sortedFUs = [...fus].sort((a, b) => {
631
+ const scoreA = a.relationship === "divorced" || a.relationship === "separated" ? 0 : 1;
632
+ const scoreB = b.relationship === "divorced" || b.relationship === "separated" ? 0 : 1;
633
+ return scoreA - scoreB;
634
+ });
635
+ for (const fu of sortedFUs) {
636
+ const partner = fu.partners[0] === id ? fu.partners[1] : fu.partners[0];
637
+ if (!placed.has(partner) && nodeSet.has(partner)) {
638
+ ordered.push(partner);
639
+ placed.add(partner);
640
+ const partnerFUs = personToFUs.get(partner) ?? [];
641
+ for (const pfu of partnerFUs) {
642
+ if (pfu.id !== fu.id) {
643
+ const otherPartner = pfu.partners[0] === partner ? pfu.partners[1] : pfu.partners[0];
644
+ placePersonAndExpand(otherPartner);
645
+ }
646
+ }
647
+ }
648
+ }
649
+ }
650
+ for (const fu of fuInGen) {
651
+ if (!placed.has(fu.partners[0]) && nodeSet.has(fu.partners[0])) {
652
+ placePersonAndExpand(fu.partners[0]);
653
+ }
654
+ if (!placed.has(fu.partners[1]) && nodeSet.has(fu.partners[1])) {
655
+ placePersonAndExpand(fu.partners[1]);
656
+ }
657
+ }
658
+ const remaining = nodeIds.filter((id) => !placed.has(id));
659
+ remaining.sort((a, b) => {
660
+ const indA = graph.individuals.get(a);
661
+ const indB = graph.individuals.get(b);
662
+ return (indA?.birthYear ?? 9999) - (indB?.birthYear ?? 9999);
663
+ });
664
+ for (const id of remaining) {
665
+ ordered.push(id);
666
+ placed.add(id);
667
+ }
668
+ return ordered;
669
+ }
670
+ var LABEL_HEIGHT = 20;
671
+ var LABEL_GAP = 6;
672
+ function assignPositions(orderedGens, graph, config) {
673
+ const positions = /* @__PURE__ */ new Map();
674
+ const { nodeWidth, nodeSpacingX } = config;
675
+ const half = nodeWidth / 2;
676
+ const coupleGap = nodeWidth + nodeSpacingX * 0.6;
677
+ const familyGap = nodeWidth + nodeSpacingX * 1.5;
678
+ const genStepY = config.nodeHeight + LABEL_HEIGHT + LABEL_GAP + config.nodeSpacingY;
679
+ for (const gen of orderedGens) {
680
+ const y = gen.index * genStepY + half;
681
+ const segments = buildSegments(gen.nodeIds, gen.index, graph);
682
+ let xCursor = half;
683
+ for (let s = 0; s < segments.length; s++) {
684
+ const seg = segments[s];
685
+ if (s > 0) xCursor += familyGap;
686
+ if (seg.type === "couple") {
687
+ positions.set(seg.ids[0], { id: seg.ids[0], x: xCursor, y, generation: gen.index });
688
+ xCursor += coupleGap;
689
+ positions.set(seg.ids[1], { id: seg.ids[1], x: xCursor, y, generation: gen.index });
690
+ } else {
691
+ positions.set(seg.ids[0], { id: seg.ids[0], x: xCursor, y, generation: gen.index });
692
+ }
693
+ }
694
+ }
695
+ resolveOverlaps(positions, orderedGens, config);
696
+ centerChildrenUnderParents(positions, graph, config);
697
+ resolveOverlaps(positions, orderedGens, config);
698
+ return positions;
699
+ }
700
+ function buildSegments(nodeIds, _genIdx, graph) {
701
+ const nodeSet = new Set(nodeIds);
702
+ const personFUs = /* @__PURE__ */ new Map();
703
+ for (const fu of graph.familyUnits) {
704
+ if (nodeSet.has(fu.partners[0]) && nodeSet.has(fu.partners[1])) {
705
+ for (const p of fu.partners) {
706
+ const arr = personFUs.get(p) ?? [];
707
+ arr.push(fu);
708
+ personFUs.set(p, arr);
709
+ }
710
+ }
711
+ }
712
+ const multiMarried = /* @__PURE__ */ new Set();
713
+ for (const [id, fus] of personFUs) {
714
+ if (fus.length > 1) multiMarried.add(id);
715
+ }
716
+ const segments = [];
717
+ const placed = /* @__PURE__ */ new Set();
718
+ for (const id of nodeIds) {
719
+ if (placed.has(id)) continue;
720
+ if (multiMarried.has(id)) {
721
+ const fus = personFUs.get(id) ?? [];
722
+ const sorted = [...fus].sort((a, b) => {
723
+ const sa = a.relationship === "divorced" || a.relationship === "separated" ? 0 : 1;
724
+ const sb = b.relationship === "divorced" || b.relationship === "separated" ? 0 : 1;
725
+ return sa - sb;
726
+ });
727
+ for (const fu2 of sorted) {
728
+ const partner = fu2.partners[0] === id ? fu2.partners[1] : fu2.partners[0];
729
+ if (!placed.has(partner)) {
730
+ if (!placed.has(id)) {
731
+ segments.push({ type: "couple", ids: [partner, id], fuId: fu2.id });
732
+ placed.add(partner);
733
+ placed.add(id);
734
+ } else {
735
+ segments.push({ type: "couple", ids: [id, partner], fuId: fu2.id });
736
+ placed.add(partner);
737
+ }
738
+ }
739
+ }
740
+ if (!placed.has(id)) {
741
+ segments.push({ type: "single", ids: [id] });
742
+ placed.add(id);
743
+ }
744
+ continue;
745
+ }
746
+ const fu = graph.familyUnits.find(
747
+ (f) => (f.partners[0] === id || f.partners[1] === id) && !placed.has(f.partners[0]) && !placed.has(f.partners[1]) && nodeSet.has(f.partners[0]) && nodeSet.has(f.partners[1])
748
+ );
749
+ if (fu) {
750
+ segments.push({
751
+ type: "couple",
752
+ ids: [fu.partners[0], fu.partners[1]],
753
+ fuId: fu.id
754
+ });
755
+ placed.add(fu.partners[0]);
756
+ placed.add(fu.partners[1]);
757
+ } else {
758
+ segments.push({ type: "single", ids: [id] });
759
+ placed.add(id);
760
+ }
761
+ }
762
+ return segments;
763
+ }
764
+ function centerChildrenUnderParents(positions, graph, config) {
765
+ const coupleGap = config.nodeSpacingX + config.nodeWidth;
766
+ const personToFUs = /* @__PURE__ */ new Map();
767
+ for (const fu of graph.familyUnits) {
768
+ for (const p of fu.partners) {
769
+ const arr = personToFUs.get(p) ?? [];
770
+ arr.push(fu);
771
+ personToFUs.set(p, arr);
772
+ }
773
+ }
774
+ for (let pass = 0; pass < 3; pass++) {
775
+ for (const fu of graph.familyUnits) {
776
+ if (fu.children.length === 0) continue;
777
+ const posA = positions.get(fu.partners[0]);
778
+ const posB = positions.get(fu.partners[1]);
779
+ if (!posA || !posB) continue;
780
+ const parentMidX = (posA.x + posB.x) / 2;
781
+ const sortedChildren = [...fu.children].sort((a, b) => {
782
+ const indA = graph.individuals.get(a);
783
+ const indB = graph.individuals.get(b);
784
+ return (indA?.birthYear ?? 9999) - (indB?.birthYear ?? 9999);
785
+ });
786
+ const childSpacing = config.nodeSpacingX + config.nodeWidth;
787
+ if (sortedChildren.length > 1) {
788
+ const gaps = [];
789
+ for (let gi = 0; gi < sortedChildren.length - 1; gi++) {
790
+ let gap = childSpacing;
791
+ const nextChild = sortedChildren[gi + 1];
792
+ const nextFUs = personToFUs.get(nextChild) ?? [];
793
+ for (const cfu of nextFUs) {
794
+ const pid = cfu.partners[0] === nextChild ? cfu.partners[1] : cfu.partners[0];
795
+ const cInd = graph.individuals.get(nextChild);
796
+ const pInd = graph.individuals.get(pid);
797
+ if (!(cInd?.sex === "male" || cInd?.sex !== "female" && pInd?.sex === "female")) {
798
+ gap += coupleGap;
799
+ }
800
+ }
801
+ const currChild = sortedChildren[gi];
802
+ const currFUs = personToFUs.get(currChild) ?? [];
803
+ for (const cfu of currFUs) {
804
+ const pid = cfu.partners[0] === currChild ? cfu.partners[1] : cfu.partners[0];
805
+ const cInd = graph.individuals.get(currChild);
806
+ const pInd = graph.individuals.get(pid);
807
+ if (cInd?.sex === "male" || cInd?.sex !== "female" && pInd?.sex === "female") {
808
+ gap += coupleGap;
809
+ }
810
+ }
811
+ gaps.push(gap);
812
+ }
813
+ const totalWidth = gaps.reduce((s, g) => s + g, 0);
814
+ const startX = parentMidX - totalWidth / 2;
815
+ let cx = startX;
816
+ for (let i = 0; i < sortedChildren.length; i++) {
817
+ const pos = positions.get(sortedChildren[i]);
818
+ if (pos) pos.x = cx;
819
+ if (i < gaps.length) cx += gaps[i];
820
+ }
821
+ } else {
822
+ const pos = positions.get(sortedChildren[0]);
823
+ if (pos) pos.x = parentMidX;
824
+ }
825
+ for (const childId of fu.children) {
826
+ const childPos = positions.get(childId);
827
+ if (!childPos) continue;
828
+ const childFUs = personToFUs.get(childId) ?? [];
829
+ for (const childFU of childFUs) {
830
+ const partnerId = childFU.partners[0] === childId ? childFU.partners[1] : childFU.partners[0];
831
+ const partnerPos = positions.get(partnerId);
832
+ if (!partnerPos) continue;
833
+ const childInd = graph.individuals.get(childId);
834
+ const partnerInd = graph.individuals.get(partnerId);
835
+ if (childInd?.sex === "male" || childInd?.sex !== "female" && partnerInd?.sex === "female") {
836
+ partnerPos.x = childPos.x + coupleGap;
837
+ } else {
838
+ partnerPos.x = childPos.x - coupleGap;
839
+ }
840
+ }
841
+ }
842
+ }
843
+ }
844
+ }
845
+ function resolveOverlaps(positions, orderedGens, config) {
846
+ const minGap = config.nodeWidth + config.nodeSpacingX;
847
+ for (const gen of orderedGens) {
848
+ const genNodes = gen.nodeIds.map((id) => positions.get(id)).filter((p) => p !== void 0);
849
+ genNodes.sort((a, b) => a.x - b.x);
850
+ for (let i = 1; i < genNodes.length; i++) {
851
+ const gap = genNodes[i].x - genNodes[i - 1].x;
852
+ if (gap < minGap) {
853
+ const shift = minGap - gap;
854
+ for (let j = i; j < genNodes.length; j++) {
855
+ genNodes[j].x += shift;
856
+ }
857
+ }
858
+ }
859
+ }
860
+ let minX = Infinity;
861
+ for (const pos of positions.values()) {
862
+ if (pos.x < minX) minX = pos.x;
863
+ }
864
+ if (minX < config.nodeWidth / 2) {
865
+ const shift = config.nodeWidth / 2 - minX;
866
+ for (const pos of positions.values()) {
867
+ pos.x += shift;
868
+ }
869
+ }
870
+ }
871
+ function computeEdges(graph, positions, config) {
872
+ const edges = [];
873
+ const half = config.nodeWidth / 2;
874
+ const dropY_offset = config.nodeHeight / 2 + LABEL_HEIGHT + LABEL_GAP + config.nodeSpacingY * 0.35;
875
+ for (const fu of graph.familyUnits) {
876
+ const posA = positions.get(fu.partners[0]);
877
+ const posB = positions.get(fu.partners[1]);
878
+ if (!posA || !posB) continue;
879
+ const leftPos = posA.x < posB.x ? posA : posB;
880
+ const rightPos = posA.x < posB.x ? posB : posA;
881
+ const leftId = posA.x < posB.x ? fu.partners[0] : fu.partners[1];
882
+ const rightId = posA.x < posB.x ? fu.partners[1] : fu.partners[0];
883
+ const coupleRel = {
884
+ type: fu.relationship,
885
+ from: leftId,
886
+ to: rightId,
887
+ label: fu.label
888
+ };
889
+ const couplePath = `M ${leftPos.x + half} ${leftPos.y} L ${rightPos.x - half} ${rightPos.y}`;
890
+ edges.push({
891
+ from: leftId,
892
+ to: rightId,
893
+ relationship: coupleRel,
894
+ path: couplePath
895
+ });
896
+ if (fu.children.length > 0) {
897
+ const midX = (posA.x + posB.x) / 2;
898
+ const coupleY = posA.y;
899
+ const dropY = coupleY + dropY_offset;
900
+ const childPositions = fu.children.map((cid) => ({
901
+ id: cid,
902
+ pos: positions.get(cid)
903
+ })).filter(
904
+ (c) => c.pos !== void 0
905
+ );
906
+ if (childPositions.length === 0) continue;
907
+ childPositions.sort((a, b) => a.pos.x - b.pos.x);
908
+ const leftX = childPositions[0].pos.x;
909
+ const rightX = childPositions[childPositions.length - 1].pos.x;
910
+ const dropPath = `M ${midX} ${coupleY} L ${midX} ${dropY}`;
911
+ edges.push({
912
+ from: fu.partners[0],
913
+ to: fu.partners[1],
914
+ relationship: { type: "parent-child", from: fu.id, to: "_drop" },
915
+ path: dropPath
916
+ });
917
+ if (childPositions.length > 1) {
918
+ const sibPath = `M ${leftX} ${dropY} L ${rightX} ${dropY}`;
919
+ edges.push({
920
+ from: fu.partners[0],
921
+ to: fu.partners[1],
922
+ relationship: { type: "parent-child", from: fu.id, to: "_sibship" },
923
+ path: sibPath
924
+ });
925
+ }
926
+ for (const child of childPositions) {
927
+ const childTop = child.pos.y - config.nodeHeight / 2;
928
+ let childPath;
929
+ if (childPositions.length === 1) {
930
+ if (Math.abs(child.pos.x - midX) < 1) {
931
+ childPath = `M ${midX} ${coupleY} L ${midX} ${childTop}`;
932
+ } else {
933
+ childPath = `M ${midX} ${dropY} L ${child.pos.x} ${dropY} L ${child.pos.x} ${childTop}`;
934
+ }
935
+ } else {
936
+ childPath = `M ${child.pos.x} ${dropY} L ${child.pos.x} ${childTop}`;
937
+ }
938
+ const pcRel = findParentChildRel(graph, fu.id, child.id);
939
+ edges.push({
940
+ from: fu.id,
941
+ to: child.id,
942
+ relationship: pcRel ?? {
943
+ type: "parent-child",
944
+ from: fu.id,
945
+ to: child.id
946
+ },
947
+ path: childPath
948
+ });
949
+ }
950
+ }
951
+ }
952
+ return edges;
953
+ }
954
+ function findParentChildRel(graph, _fuId, childId) {
955
+ const fuId = graph.childOf.get(childId);
956
+ if (!fuId) return void 0;
957
+ return {
958
+ type: "parent-child",
959
+ from: fuId,
960
+ to: childId
961
+ };
962
+ }
963
+ function packageResult(positions, edges, graph, config) {
964
+ const padding = 40;
965
+ const nodes = [];
966
+ for (const [id, pos] of positions) {
967
+ const ind = graph.individuals.get(id);
968
+ if (!ind) continue;
969
+ nodes.push({
970
+ id,
971
+ x: pos.x - config.nodeWidth / 2 + padding,
972
+ y: pos.y - config.nodeHeight / 2 + padding,
973
+ width: config.nodeWidth,
974
+ height: config.nodeHeight,
975
+ generation: pos.generation,
976
+ individual: ind
977
+ });
978
+ }
979
+ const shiftedEdges = edges.map((e) => ({
980
+ ...e,
981
+ path: shiftPath(e.path, padding, padding)
982
+ }));
983
+ let maxX = 0;
984
+ let maxY = 0;
985
+ let minX = Infinity;
986
+ for (const node of nodes) {
987
+ const cx = node.x + node.width / 2;
988
+ const labelText = estimateLabelText(node.individual);
989
+ const labelHalfWidth = labelText.length * 3.8;
990
+ const right = Math.max(node.x + node.width, cx + labelHalfWidth);
991
+ const left = Math.min(node.x, cx - labelHalfWidth);
992
+ const bottom = node.y + node.height;
993
+ if (right > maxX) maxX = right;
994
+ if (bottom > maxY) maxY = bottom;
995
+ if (left < minX) minX = left;
996
+ }
997
+ if (minX < 0) {
998
+ const shift = -minX;
999
+ for (const node of nodes) {
1000
+ node.x += shift;
1001
+ }
1002
+ for (const edge of shiftedEdges) {
1003
+ edge.path = shiftPath(edge.path, shift, 0);
1004
+ }
1005
+ maxX += shift;
1006
+ }
1007
+ return {
1008
+ width: maxX + padding,
1009
+ height: maxY + padding + LABEL_GAP + LABEL_HEIGHT + 10,
1010
+ nodes,
1011
+ edges: shiftedEdges
1012
+ };
1013
+ }
1014
+ function estimateLabelText(ind) {
1015
+ const name = ind.label || ind.id;
1016
+ if (ind.birthYear && ind.deathYear) return `${name} (${ind.birthYear}\u2013${ind.deathYear})`;
1017
+ if (ind.birthYear) return `${name} (b. ${ind.birthYear})`;
1018
+ return name;
1019
+ }
1020
+ var EMOTIONAL_REL_TYPES = /* @__PURE__ */ new Set([
1021
+ "harmony",
1022
+ "close",
1023
+ "bestfriends",
1024
+ "love",
1025
+ "inlove",
1026
+ "friendship",
1027
+ "hostile",
1028
+ "conflict",
1029
+ "enmity",
1030
+ "distant-hostile",
1031
+ "cutoff",
1032
+ "close-hostile",
1033
+ "fused",
1034
+ "fused-hostile",
1035
+ "distant",
1036
+ "normal",
1037
+ "nevermet",
1038
+ "abuse",
1039
+ "physical-abuse",
1040
+ "emotional-abuse",
1041
+ "sexual-abuse",
1042
+ "neglect",
1043
+ "manipulative",
1044
+ "controlling",
1045
+ "jealous",
1046
+ "focused",
1047
+ "focused-neg",
1048
+ "distrust",
1049
+ "admirer",
1050
+ "limerence"
1051
+ ]);
1052
+ function computeEmotionalEdges(relationships, positions, config) {
1053
+ const edges = [];
1054
+ const half = config.nodeWidth / 2;
1055
+ const emotionalRels = relationships.filter((r) => EMOTIONAL_REL_TYPES.has(r.type));
1056
+ for (const rel of emotionalRels) {
1057
+ const posA = positions.get(rel.from);
1058
+ const posB = positions.get(rel.to);
1059
+ if (!posA || !posB) continue;
1060
+ const ax = posA.x;
1061
+ const ay = posA.y;
1062
+ const bx = posB.x;
1063
+ const by = posB.y;
1064
+ let pathData;
1065
+ if (posA.generation === posB.generation) {
1066
+ const midX = (ax + bx) / 2;
1067
+ const curveY = ay + half + 30;
1068
+ pathData = `M ${ax} ${ay + half} Q ${midX} ${curveY} ${bx} ${by + half}`;
1069
+ } else {
1070
+ const midY = (ay + by) / 2;
1071
+ const maxX = Math.max(ax, bx);
1072
+ const curveX = maxX + half + 30;
1073
+ pathData = `M ${ax} ${ay} Q ${curveX} ${midY} ${bx} ${by}`;
1074
+ }
1075
+ edges.push({
1076
+ from: rel.from,
1077
+ to: rel.to,
1078
+ relationship: rel,
1079
+ path: pathData
1080
+ });
1081
+ }
1082
+ return edges;
1083
+ }
1084
+ function shiftPath(pathData, dx, dy) {
1085
+ return pathData.replace(
1086
+ /([\d.-]+)\s+([\d.-]+)/g,
1087
+ (_match, xStr, yStr) => {
1088
+ const x = parseFloat(xStr) + dx;
1089
+ const y = parseFloat(yStr) + dy;
1090
+ return `${x} ${y}`;
1091
+ }
1092
+ );
1093
+ }
1094
+
1095
+ // src/diagrams/genogram/symbols.ts
1096
+ function renderIndividualSymbol(individual, x, y, size) {
1097
+ const half = size / 2;
1098
+ const classes = [
1099
+ "schematex-genogram-node",
1100
+ `schematex-genogram-${individual.sex === "other" ? "unknown" : individual.sex}`
1101
+ ];
1102
+ if (individual.status === "deceased") classes.push("schematex-genogram-deceased");
1103
+ const titleText = formatTitle(individual);
1104
+ const children = [chunkHDKDQAEQ_cjs.title(titleText)];
1105
+ const isIndex = individual.markers?.includes("index-person");
1106
+ if (isIndex) {
1107
+ children.push(indexBorder(individual.sex, half));
1108
+ classes.push("schematex-genogram-index-person");
1109
+ }
1110
+ children.push(baseShape(individual.sex, half));
1111
+ if (individual.conditions?.length) {
1112
+ for (const cond of individual.conditions) {
1113
+ children.push(conditionFillElement(individual.sex, half, cond));
1114
+ }
1115
+ }
1116
+ if (individual.status === "deceased") {
1117
+ children.push(...deceasedOverlay(individual.sex, half));
1118
+ }
1119
+ const ageToShow = individual.age ?? calcAge(individual);
1120
+ if (ageToShow !== void 0) {
1121
+ children.push(
1122
+ chunkHDKDQAEQ_cjs.text(
1123
+ {
1124
+ x: 0,
1125
+ y: 5,
1126
+ class: "schematex-genogram-age",
1127
+ "text-anchor": "middle",
1128
+ "font-size": "11"
1129
+ },
1130
+ String(ageToShow)
1131
+ )
1132
+ );
1133
+ }
1134
+ return chunkHDKDQAEQ_cjs.group(
1135
+ {
1136
+ class: classes.join(" "),
1137
+ "data-individual-id": individual.id,
1138
+ transform: `translate(${x}, ${y})`
1139
+ },
1140
+ children
1141
+ );
1142
+ }
1143
+ function getRequiredDefs(individuals, includeArrowMarker = false) {
1144
+ const neededFills = /* @__PURE__ */ new Set();
1145
+ for (const ind of individuals) {
1146
+ if (ind.conditions) {
1147
+ for (const cond of ind.conditions) {
1148
+ neededFills.add(cond.fill);
1149
+ }
1150
+ }
1151
+ }
1152
+ const children = [];
1153
+ if (neededFills.has("half-left")) {
1154
+ children.push(
1155
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-half-left-rect" }, [
1156
+ chunkHDKDQAEQ_cjs.rect({ x: "0", y: "0", width: "50%", height: "100%" })
1157
+ ]),
1158
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-half-left-circle" }, [
1159
+ chunkHDKDQAEQ_cjs.rect({ x: "-50", y: "-50", width: "50", height: "100" })
1160
+ ])
1161
+ );
1162
+ }
1163
+ if (neededFills.has("half-right")) {
1164
+ children.push(
1165
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-half-right-rect" }, [
1166
+ chunkHDKDQAEQ_cjs.rect({ x: "50%", y: "0", width: "50%", height: "100%" })
1167
+ ]),
1168
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-half-right-circle" }, [
1169
+ chunkHDKDQAEQ_cjs.rect({ x: "0", y: "-50", width: "50", height: "100" })
1170
+ ])
1171
+ );
1172
+ }
1173
+ if (neededFills.has("half-bottom")) {
1174
+ children.push(
1175
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-half-bottom-rect" }, [
1176
+ chunkHDKDQAEQ_cjs.rect({ x: "0", y: "50%", width: "100%", height: "50%" })
1177
+ ]),
1178
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-half-bottom-circle" }, [
1179
+ chunkHDKDQAEQ_cjs.rect({ x: "-50", y: "0", width: "100", height: "50" })
1180
+ ])
1181
+ );
1182
+ }
1183
+ if (neededFills.has("quarter")) {
1184
+ children.push(
1185
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-quarter-rect" }, [
1186
+ chunkHDKDQAEQ_cjs.rect({ x: "0", y: "0", width: "50%", height: "50%" })
1187
+ ]),
1188
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-quarter-circle" }, [
1189
+ chunkHDKDQAEQ_cjs.rect({ x: "-50", y: "-50", width: "50", height: "50" })
1190
+ ])
1191
+ );
1192
+ }
1193
+ if (neededFills.has("half-top")) {
1194
+ children.push(
1195
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-half-top-rect" }, [
1196
+ chunkHDKDQAEQ_cjs.rect({ x: "0", y: "0", width: "100%", height: "50%" })
1197
+ ]),
1198
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-half-top-circle" }, [
1199
+ chunkHDKDQAEQ_cjs.rect({ x: "-50", y: "-50", width: "100", height: "50" })
1200
+ ])
1201
+ );
1202
+ }
1203
+ if (neededFills.has("quad-tl")) {
1204
+ children.push(
1205
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-quad-tl-rect" }, [
1206
+ chunkHDKDQAEQ_cjs.rect({ x: "0", y: "0", width: "50%", height: "50%" })
1207
+ ]),
1208
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-quad-tl-circle" }, [
1209
+ chunkHDKDQAEQ_cjs.rect({ x: "-50", y: "-50", width: "50", height: "50" })
1210
+ ])
1211
+ );
1212
+ }
1213
+ if (neededFills.has("quad-tr")) {
1214
+ children.push(
1215
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-quad-tr-rect" }, [
1216
+ chunkHDKDQAEQ_cjs.rect({ x: "50%", y: "0", width: "50%", height: "50%" })
1217
+ ]),
1218
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-quad-tr-circle" }, [
1219
+ chunkHDKDQAEQ_cjs.rect({ x: "0", y: "-50", width: "50", height: "50" })
1220
+ ])
1221
+ );
1222
+ }
1223
+ if (neededFills.has("quad-bl")) {
1224
+ children.push(
1225
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-quad-bl-rect" }, [
1226
+ chunkHDKDQAEQ_cjs.rect({ x: "0", y: "50%", width: "50%", height: "50%" })
1227
+ ]),
1228
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-quad-bl-circle" }, [
1229
+ chunkHDKDQAEQ_cjs.rect({ x: "-50", y: "0", width: "50", height: "50" })
1230
+ ])
1231
+ );
1232
+ }
1233
+ if (neededFills.has("quad-br")) {
1234
+ children.push(
1235
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-quad-br-rect" }, [
1236
+ chunkHDKDQAEQ_cjs.rect({ x: "50%", y: "50%", width: "50%", height: "50%" })
1237
+ ]),
1238
+ chunkHDKDQAEQ_cjs.el("clipPath", { id: "schematex-genogram-clip-quad-br-circle" }, [
1239
+ chunkHDKDQAEQ_cjs.rect({ x: "0", y: "0", width: "50", height: "50" })
1240
+ ])
1241
+ );
1242
+ }
1243
+ if (neededFills.has("striped")) {
1244
+ children.push(
1245
+ chunkHDKDQAEQ_cjs.pattern(
1246
+ {
1247
+ id: "schematex-genogram-pattern-striped",
1248
+ patternUnits: "userSpaceOnUse",
1249
+ width: "4",
1250
+ height: "4"
1251
+ },
1252
+ [
1253
+ chunkHDKDQAEQ_cjs.path({
1254
+ d: "M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2",
1255
+ stroke: "#333",
1256
+ "stroke-width": "1"
1257
+ })
1258
+ ]
1259
+ )
1260
+ );
1261
+ }
1262
+ if (neededFills.has("dotted")) {
1263
+ children.push(
1264
+ chunkHDKDQAEQ_cjs.pattern(
1265
+ {
1266
+ id: "schematex-genogram-pattern-dotted",
1267
+ patternUnits: "userSpaceOnUse",
1268
+ width: "6",
1269
+ height: "6"
1270
+ },
1271
+ [
1272
+ chunkHDKDQAEQ_cjs.circle({ cx: "3", cy: "3", r: "1", fill: "#333" })
1273
+ ]
1274
+ )
1275
+ );
1276
+ }
1277
+ if (includeArrowMarker) {
1278
+ children.push(
1279
+ chunkHDKDQAEQ_cjs.el("marker", {
1280
+ id: "schematex-genogram-arrow",
1281
+ viewBox: "0 0 10 10",
1282
+ refX: "10",
1283
+ refY: "5",
1284
+ markerWidth: "6",
1285
+ markerHeight: "6",
1286
+ orient: "auto"
1287
+ }, [
1288
+ chunkHDKDQAEQ_cjs.path({ d: "M 0 0 L 10 5 L 0 10 z", fill: "#333" })
1289
+ ])
1290
+ );
1291
+ }
1292
+ return chunkHDKDQAEQ_cjs.defs(children);
1293
+ }
1294
+ function capitalize(s) {
1295
+ return s.charAt(0).toUpperCase() + s.slice(1);
1296
+ }
1297
+ function formatTitle(ind) {
1298
+ const name = capitalize(ind.label || ind.id);
1299
+ if (ind.birthYear && ind.deathYear) {
1300
+ return `${name} (${ind.birthYear}-${ind.deathYear})`;
1301
+ }
1302
+ if (ind.birthYear) {
1303
+ return `${name} (${ind.birthYear})`;
1304
+ }
1305
+ return name;
1306
+ }
1307
+ function calcAge(ind) {
1308
+ if (!ind.birthYear) return void 0;
1309
+ if (ind.deathYear) return ind.deathYear - ind.birthYear;
1310
+ if (ind.status === "deceased") return void 0;
1311
+ return void 0;
1312
+ }
1313
+ function indexBorder(sex, half) {
1314
+ const outer = half + 4;
1315
+ switch (sex) {
1316
+ case "male":
1317
+ return chunkHDKDQAEQ_cjs.rect({
1318
+ x: -outer,
1319
+ y: -outer,
1320
+ width: outer * 2,
1321
+ height: outer * 2,
1322
+ class: "schematex-genogram-index-border"
1323
+ });
1324
+ case "female":
1325
+ return chunkHDKDQAEQ_cjs.circle({
1326
+ cx: 0,
1327
+ cy: 0,
1328
+ r: outer,
1329
+ class: "schematex-genogram-index-border"
1330
+ });
1331
+ case "unknown":
1332
+ case "other":
1333
+ case "nonbinary":
1334
+ case "intersex":
1335
+ return chunkHDKDQAEQ_cjs.polygon({
1336
+ points: `0,${-outer} ${outer},0 0,${outer} ${-outer},0`,
1337
+ class: "schematex-genogram-index-border"
1338
+ });
1339
+ }
1340
+ }
1341
+ function baseShape(sex, half) {
1342
+ switch (sex) {
1343
+ case "male":
1344
+ return chunkHDKDQAEQ_cjs.rect({
1345
+ x: -half,
1346
+ y: -half,
1347
+ width: half * 2,
1348
+ height: half * 2,
1349
+ class: "schematex-genogram-shape"
1350
+ });
1351
+ case "female":
1352
+ return chunkHDKDQAEQ_cjs.circle({
1353
+ cx: 0,
1354
+ cy: 0,
1355
+ r: half,
1356
+ class: "schematex-genogram-shape"
1357
+ });
1358
+ case "unknown":
1359
+ case "other":
1360
+ case "nonbinary":
1361
+ case "intersex":
1362
+ return chunkHDKDQAEQ_cjs.polygon({
1363
+ points: `0,${-half} ${half},0 0,${half} ${-half},0`,
1364
+ class: "schematex-genogram-shape"
1365
+ });
1366
+ }
1367
+ }
1368
+ function deceasedOverlay(sex, half) {
1369
+ const extend = sex === "female" ? half * 0.707 : half;
1370
+ return [
1371
+ chunkHDKDQAEQ_cjs.line({
1372
+ x1: -extend,
1373
+ y1: -extend,
1374
+ x2: extend,
1375
+ y2: extend,
1376
+ class: "schematex-genogram-deceased-mark",
1377
+ stroke: "#333",
1378
+ "stroke-width": "2"
1379
+ }),
1380
+ chunkHDKDQAEQ_cjs.line({
1381
+ x1: extend,
1382
+ y1: -extend,
1383
+ x2: -extend,
1384
+ y2: extend,
1385
+ class: "schematex-genogram-deceased-mark",
1386
+ stroke: "#333",
1387
+ "stroke-width": "2"
1388
+ })
1389
+ ];
1390
+ }
1391
+ function conditionFillElement(sex, half, cond) {
1392
+ const fillColor = cond.color ?? "#333";
1393
+ const attrs = {};
1394
+ if (cond.fill === "full") {
1395
+ attrs.fill = fillColor;
1396
+ } else if (cond.fill === "striped") {
1397
+ attrs.fill = "url(#schematex-genogram-pattern-striped)";
1398
+ } else if (cond.fill === "dotted") {
1399
+ attrs.fill = "url(#schematex-genogram-pattern-dotted)";
1400
+ } else {
1401
+ const clipSuffix = sex === "female" ? "circle" : "rect";
1402
+ attrs["clip-path"] = `url(#schematex-genogram-clip-${cond.fill}-${clipSuffix})`;
1403
+ attrs.fill = fillColor;
1404
+ }
1405
+ attrs.class = `schematex-genogram-condition-fill schematex-genogram-condition-${cond.label}`;
1406
+ switch (sex) {
1407
+ case "male":
1408
+ return chunkHDKDQAEQ_cjs.rect({ x: -half, y: -half, width: half * 2, height: half * 2, ...attrs });
1409
+ case "female":
1410
+ return chunkHDKDQAEQ_cjs.circle({ cx: 0, cy: 0, r: half, ...attrs });
1411
+ case "unknown":
1412
+ case "other":
1413
+ case "nonbinary":
1414
+ case "intersex":
1415
+ default:
1416
+ return chunkHDKDQAEQ_cjs.polygon({
1417
+ points: `0,${-half} ${half},0 0,${half} ${-half},0`,
1418
+ ...attrs
1419
+ });
1420
+ }
1421
+ }
1422
+
1423
+ // src/diagrams/genogram/renderer.ts
1424
+ function renderGenogram(layout, config, ast) {
1425
+ const hasDirectional = layout.edges.some((e) => e.relationship.directional);
1426
+ const defsStr = getRequiredDefs(layout.nodes.map((n) => n.individual), hasDirectional);
1427
+ const styleStr = buildStyles(config);
1428
+ const genGroups = groupByGeneration(layout.nodes);
1429
+ const structuralEdges = layout.edges.filter((e) => !EMOTIONAL_REL_TYPES2.has(e.relationship.type));
1430
+ const emotionalEdges = layout.edges.filter((e) => EMOTIONAL_REL_TYPES2.has(e.relationship.type));
1431
+ const edgeLayers = renderEdges(structuralEdges);
1432
+ const emotionalLayer = renderEmotionalEdges(emotionalEdges);
1433
+ const nodeLayers = renderNodes(genGroups);
1434
+ const labelLayer = renderLabels(layout.nodes, config);
1435
+ const edgeLabelLayer = renderEdgeLabels(structuralEdges, config);
1436
+ const nodeCount = layout.nodes.length;
1437
+ const genCount = genGroups.size;
1438
+ const chartTitle = ast?.metadata?.title;
1439
+ const titleHeight = chartTitle ? 40 : 0;
1440
+ const totalHeight = layout.height + titleHeight;
1441
+ const layers = [
1442
+ chunkHDKDQAEQ_cjs.title(chartTitle ? `Genogram: ${chartTitle}` : "Genogram"),
1443
+ chunkHDKDQAEQ_cjs.desc(
1444
+ `Genogram diagram with ${nodeCount} individuals across ${genCount} generations`
1445
+ ),
1446
+ defsStr,
1447
+ styleStr
1448
+ ];
1449
+ if (chartTitle) {
1450
+ layers.push(
1451
+ chunkHDKDQAEQ_cjs.text(
1452
+ {
1453
+ x: layout.width / 2,
1454
+ y: 28,
1455
+ class: "schematex-genogram-title",
1456
+ "text-anchor": "middle",
1457
+ "font-size": "20",
1458
+ "font-weight": "bold",
1459
+ "font-family": config.fontFamily
1460
+ },
1461
+ chartTitle
1462
+ )
1463
+ );
1464
+ }
1465
+ const contentGroup = chunkHDKDQAEQ_cjs.group(
1466
+ { transform: titleHeight > 0 ? `translate(0, ${titleHeight})` : void 0 },
1467
+ [edgeLayers, emotionalLayer, ...nodeLayers, labelLayer, edgeLabelLayer]
1468
+ );
1469
+ layers.push(contentGroup);
1470
+ return chunkHDKDQAEQ_cjs.svgRoot(
1471
+ {
1472
+ viewBox: `0 0 ${layout.width} ${totalHeight}`,
1473
+ class: "schematex-diagram schematex-genogram",
1474
+ width: layout.width,
1475
+ height: totalHeight
1476
+ },
1477
+ layers
1478
+ );
1479
+ }
1480
+ function buildStyles(config) {
1481
+ const t = chunkN7KOXOMX_cjs.resolveGenogramTheme(config.theme);
1482
+ const css = `
1483
+ .schematex-genogram {${chunkN7KOXOMX_cjs.cssCustomProperties(t)}
1484
+ background: ${t.bg};
1485
+ }
1486
+ .schematex-genogram-shape { fill: ${t.fill}; stroke: ${t.stroke}; stroke-width: ${chunkN7KOXOMX_cjs.STROKE_WIDTH.normal}; stroke-linejoin: round; }
1487
+ .schematex-genogram-male .schematex-genogram-shape { fill: ${t.maleFill}; }
1488
+ .schematex-genogram-female .schematex-genogram-shape { fill: ${t.femaleFill}; }
1489
+ .schematex-genogram-unknown .schematex-genogram-shape { fill: ${t.unknownFill}; }
1490
+ .schematex-genogram-label { font-family: ${config.fontFamily}; font-size: ${config.fontSize}px; text-anchor: middle; fill: ${t.text}; }
1491
+ .schematex-genogram-edge { stroke: ${t.neutral}; stroke-width: ${chunkN7KOXOMX_cjs.STROKE_WIDTH.normal}; fill: none; stroke-linecap: round; stroke-linejoin: round; }
1492
+ .schematex-genogram-edge-cohabiting path { stroke-dasharray: 6,4; }
1493
+ .schematex-genogram-edge-divorced .schematex-genogram-divorce-mark { stroke: ${t.neutral}; stroke-width: ${chunkN7KOXOMX_cjs.STROKE_WIDTH.normal}; }
1494
+ .schematex-genogram-edge-separated .schematex-genogram-separation-mark { stroke: ${t.neutral}; stroke-width: ${chunkN7KOXOMX_cjs.STROKE_WIDTH.normal}; }
1495
+ .schematex-genogram-deceased-mark { stroke: ${t.deceasedMark}; stroke-width: ${chunkN7KOXOMX_cjs.STROKE_WIDTH.normal}; stroke-linecap: round; }
1496
+ .schematex-genogram-condition-fill { fill: ${t.conditionFill}; }
1497
+ .schematex-genogram-age { font-family: ${config.fontFamily}; fill: ${t.text}; pointer-events: none; }
1498
+ .schematex-genogram-title { fill: ${t.text}; }
1499
+ .schematex-genogram-edge-label { font-family: ${config.fontFamily}; fill: ${t.text}; }
1500
+ .schematex-genogram-index-border { stroke: ${t.warn}; stroke-width: ${chunkN7KOXOMX_cjs.STROKE_WIDTH.thick}; fill: none; }
1501
+ `;
1502
+ return chunkHDKDQAEQ_cjs.el("style", {}, css);
1503
+ }
1504
+ var EMOTIONAL_REL_TYPES2 = /* @__PURE__ */ new Set([
1505
+ "harmony",
1506
+ "close",
1507
+ "bestfriends",
1508
+ "love",
1509
+ "inlove",
1510
+ "friendship",
1511
+ "hostile",
1512
+ "conflict",
1513
+ "enmity",
1514
+ "distant-hostile",
1515
+ "cutoff",
1516
+ "close-hostile",
1517
+ "fused",
1518
+ "fused-hostile",
1519
+ "distant",
1520
+ "normal",
1521
+ "nevermet",
1522
+ "abuse",
1523
+ "physical-abuse",
1524
+ "emotional-abuse",
1525
+ "sexual-abuse",
1526
+ "neglect",
1527
+ "manipulative",
1528
+ "controlling",
1529
+ "jealous",
1530
+ "focused",
1531
+ "focused-neg",
1532
+ "distrust",
1533
+ "admirer",
1534
+ "limerence"
1535
+ ]);
1536
+ function getEmotionalColor(type) {
1537
+ if (["harmony", "close", "bestfriends", "love", "inlove", "friendship"].includes(type)) return "#4caf50";
1538
+ if (["hostile", "conflict", "enmity", "distant-hostile", "cutoff"].includes(type)) return "#e53935";
1539
+ if (["close-hostile", "fused", "fused-hostile"].includes(type)) return "#9c27b0";
1540
+ if (["distant", "normal", "nevermet"].includes(type)) return "#9e9e9e";
1541
+ if (["abuse", "physical-abuse", "emotional-abuse", "sexual-abuse", "neglect"].includes(type)) return "#b71c1c";
1542
+ if (["manipulative", "controlling", "jealous"].includes(type)) return "#e65100";
1543
+ return "#1565c0";
1544
+ }
1545
+ function getEmotionalLineStyle(type) {
1546
+ if (["hostile", "conflict", "enmity", "distant-hostile", "close-hostile", "fused-hostile"].includes(type)) {
1547
+ return "stroke-dasharray: 8,3,2,3;";
1548
+ }
1549
+ if (["distant", "distant-hostile", "nevermet"].includes(type)) {
1550
+ return "stroke-dasharray: 6,4;";
1551
+ }
1552
+ if (type === "cutoff") {
1553
+ return "stroke-dasharray: 2,8;";
1554
+ }
1555
+ return "";
1556
+ }
1557
+ function getEmotionalStrokeWidth(type) {
1558
+ if (["fused", "fused-hostile", "bestfriends"].includes(type)) return 4;
1559
+ if (["close", "close-hostile", "love", "inlove"].includes(type)) return 3;
1560
+ return 2;
1561
+ }
1562
+ function renderEmotionalEdges(edges) {
1563
+ if (edges.length === 0) return chunkHDKDQAEQ_cjs.group({ class: "schematex-genogram-emotional-edges" }, []);
1564
+ const children = [];
1565
+ for (const edge of edges) {
1566
+ const type = edge.relationship.type;
1567
+ const color = getEmotionalColor(type);
1568
+ const lineStyle = getEmotionalLineStyle(type);
1569
+ const strokeWidth = getEmotionalStrokeWidth(type);
1570
+ const directional = edge.relationship.directional;
1571
+ const elements = [
1572
+ chunkHDKDQAEQ_cjs.el("path", {
1573
+ d: edge.path,
1574
+ fill: "none",
1575
+ stroke: color,
1576
+ "stroke-width": strokeWidth,
1577
+ style: lineStyle || void 0,
1578
+ "marker-end": directional ? "url(#schematex-genogram-arrow)" : void 0
1579
+ })
1580
+ ];
1581
+ if (edge.relationship.label) {
1582
+ const mid = pathMidpoint(edge.path);
1583
+ if (mid) {
1584
+ elements.push(
1585
+ chunkHDKDQAEQ_cjs.text(
1586
+ {
1587
+ x: mid.x,
1588
+ y: mid.y - 6,
1589
+ class: "schematex-genogram-edge-label",
1590
+ "text-anchor": "middle",
1591
+ "font-size": "10",
1592
+ fill: color
1593
+ },
1594
+ edge.relationship.label
1595
+ )
1596
+ );
1597
+ }
1598
+ }
1599
+ children.push(
1600
+ chunkHDKDQAEQ_cjs.group(
1601
+ {
1602
+ class: `schematex-genogram-emotional schematex-genogram-emotional-${type}`,
1603
+ "data-from": edge.from,
1604
+ "data-to": edge.to,
1605
+ "data-relationship-type": type
1606
+ },
1607
+ elements
1608
+ )
1609
+ );
1610
+ }
1611
+ return chunkHDKDQAEQ_cjs.group({ class: "schematex-genogram-emotional-edges" }, children);
1612
+ }
1613
+ function renderEdgeLabels(edges, config) {
1614
+ const labels = [];
1615
+ for (const edge of edges) {
1616
+ if (!edge.relationship.label) continue;
1617
+ const mid = pathMidpoint(edge.path);
1618
+ if (!mid) continue;
1619
+ labels.push(
1620
+ chunkHDKDQAEQ_cjs.text(
1621
+ {
1622
+ x: mid.x,
1623
+ y: mid.y - 6,
1624
+ class: "schematex-genogram-edge-label",
1625
+ "text-anchor": "middle",
1626
+ "font-size": "10",
1627
+ "font-family": config.fontFamily
1628
+ },
1629
+ edge.relationship.label
1630
+ )
1631
+ );
1632
+ }
1633
+ return chunkHDKDQAEQ_cjs.group({ class: "schematex-genogram-edge-labels" }, labels);
1634
+ }
1635
+ function renderEdges(edges) {
1636
+ const children = [];
1637
+ for (const edge of edges) {
1638
+ const relType = edge.relationship.type;
1639
+ const cssClass = `schematex-genogram-edge schematex-genogram-edge-${relType}`;
1640
+ const elements = [
1641
+ chunkHDKDQAEQ_cjs.el("path", { d: edge.path, class: "schematex-genogram-edge-path" })
1642
+ ];
1643
+ if (relType === "divorced") {
1644
+ const mid = pathMidpoint(edge.path);
1645
+ if (mid) {
1646
+ elements.push(
1647
+ chunkHDKDQAEQ_cjs.el("line", {
1648
+ x1: mid.x - 4,
1649
+ y1: mid.y - 6,
1650
+ x2: mid.x + 4,
1651
+ y2: mid.y + 6,
1652
+ class: "schematex-genogram-divorce-mark",
1653
+ stroke: "#333",
1654
+ "stroke-width": "2"
1655
+ }),
1656
+ chunkHDKDQAEQ_cjs.el("line", {
1657
+ x1: mid.x - 4 + 6,
1658
+ y1: mid.y - 6,
1659
+ x2: mid.x + 4 + 6,
1660
+ y2: mid.y + 6,
1661
+ class: "schematex-genogram-divorce-mark",
1662
+ stroke: "#333",
1663
+ "stroke-width": "2"
1664
+ })
1665
+ );
1666
+ }
1667
+ }
1668
+ if (relType === "separated") {
1669
+ const mid = pathMidpoint(edge.path);
1670
+ if (mid) {
1671
+ elements.push(
1672
+ chunkHDKDQAEQ_cjs.el("line", {
1673
+ x1: mid.x - 4,
1674
+ y1: mid.y - 6,
1675
+ x2: mid.x + 4,
1676
+ y2: mid.y + 6,
1677
+ class: "schematex-genogram-separation-mark",
1678
+ stroke: "#333",
1679
+ "stroke-width": "2"
1680
+ })
1681
+ );
1682
+ }
1683
+ }
1684
+ children.push(
1685
+ chunkHDKDQAEQ_cjs.group(
1686
+ {
1687
+ class: cssClass,
1688
+ "data-from": edge.from,
1689
+ "data-to": edge.to
1690
+ },
1691
+ elements
1692
+ )
1693
+ );
1694
+ }
1695
+ return chunkHDKDQAEQ_cjs.group({ class: "schematex-genogram-edges" }, children);
1696
+ }
1697
+ function pathMidpoint(pathData) {
1698
+ const coords = pathData.match(/[\d.-]+/g);
1699
+ if (!coords || coords.length < 4) return null;
1700
+ const x1 = parseFloat(coords[0]);
1701
+ const y1 = parseFloat(coords[1]);
1702
+ const x2 = parseFloat(coords[2]);
1703
+ const y2 = parseFloat(coords[3]);
1704
+ return { x: (x1 + x2) / 2, y: (y1 + y2) / 2 };
1705
+ }
1706
+ function groupByGeneration(nodes) {
1707
+ const groups = /* @__PURE__ */ new Map();
1708
+ for (const node of nodes) {
1709
+ const gen = node.generation;
1710
+ const group2 = groups.get(gen) ?? [];
1711
+ group2.push(node);
1712
+ groups.set(gen, group2);
1713
+ }
1714
+ return groups;
1715
+ }
1716
+ function renderNodes(genGroups) {
1717
+ const layers = [];
1718
+ const sortedGens = Array.from(genGroups.keys()).sort((a, b) => a - b);
1719
+ for (const genIdx of sortedGens) {
1720
+ const nodes = genGroups.get(genIdx) ?? [];
1721
+ const nodeElements = [];
1722
+ for (const node of nodes) {
1723
+ const cx = node.x + node.width / 2;
1724
+ const cy = node.y + node.height / 2;
1725
+ nodeElements.push(
1726
+ renderIndividualSymbol(node.individual, cx, cy, node.width)
1727
+ );
1728
+ }
1729
+ layers.push(
1730
+ chunkHDKDQAEQ_cjs.group(
1731
+ {
1732
+ class: `schematex-genogram-generation schematex-genogram-generation-${genIdx}`,
1733
+ "data-generation": genIdx
1734
+ },
1735
+ nodeElements
1736
+ )
1737
+ );
1738
+ }
1739
+ return layers;
1740
+ }
1741
+ function renderLabels(nodes, config) {
1742
+ const labels = [];
1743
+ for (const node of nodes) {
1744
+ const ind = node.individual;
1745
+ const label = ind.label || ind.id;
1746
+ const cx = node.x + node.width / 2;
1747
+ const labelY = node.y + node.height + 6 + config.fontSize;
1748
+ let labelText = label.charAt(0).toUpperCase() + label.slice(1);
1749
+ if (ind.birthYear && ind.deathYear) {
1750
+ labelText += ` (${ind.birthYear}\u2013${ind.deathYear})`;
1751
+ } else if (ind.birthYear) {
1752
+ labelText += ` (b. ${ind.birthYear})`;
1753
+ }
1754
+ labels.push(
1755
+ chunkHDKDQAEQ_cjs.text(
1756
+ {
1757
+ x: cx,
1758
+ y: labelY,
1759
+ class: "schematex-genogram-label",
1760
+ "data-individual-id": ind.id
1761
+ },
1762
+ labelText
1763
+ )
1764
+ );
1765
+ }
1766
+ return chunkHDKDQAEQ_cjs.group({ class: "schematex-genogram-labels" }, labels);
1767
+ }
1768
+
1769
+ // src/diagrams/genogram/index.ts
1770
+ var genogram = {
1771
+ type: "genogram",
1772
+ detect(text2) {
1773
+ const firstLine = text2.trim().split("\n")[0]?.trim().toLowerCase() ?? "";
1774
+ return firstLine === "genogram" || firstLine.startsWith("genogram ");
1775
+ },
1776
+ render(text2, config) {
1777
+ const ast = parseGenogram(text2);
1778
+ const layoutConfig = {
1779
+ nodeSpacingX: 80,
1780
+ nodeSpacingY: 100,
1781
+ nodeWidth: 40,
1782
+ nodeHeight: 40
1783
+ };
1784
+ const layout = layoutGenogram(ast, layoutConfig);
1785
+ const renderConfig = {
1786
+ fontFamily: config?.fontFamily ?? "system-ui, -apple-system, sans-serif",
1787
+ fontSize: config?.fontSize ?? 12,
1788
+ theme: config?.theme ?? "default",
1789
+ padding: config?.padding ?? 20
1790
+ };
1791
+ return renderGenogram(layout, renderConfig, ast);
1792
+ }
1793
+ };
1794
+
1795
+ exports.ParseError = ParseError;
1796
+ exports.genogram = genogram;
1797
+ exports.getRequiredDefs = getRequiredDefs;
1798
+ exports.layoutGenogram = layoutGenogram;
1799
+ exports.parseGenogram = parseGenogram;
1800
+ exports.renderGenogram = renderGenogram;
1801
+ exports.renderIndividualSymbol = renderIndividualSymbol;
1802
+ //# sourceMappingURL=chunk-2UKC6ZCY.cjs.map
1803
+ //# sourceMappingURL=chunk-2UKC6ZCY.cjs.map