@transitrix/diagrams 1.10.0 → 1.12.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 (71) hide show
  1. package/dist/activities/resolver.d.ts +3 -0
  2. package/dist/activities/resolver.js +39 -18
  3. package/dist/activities/validate.js +67 -4
  4. package/dist/canon-resolver-utils.d.ts +3 -0
  5. package/dist/canon-resolver-utils.js +16 -0
  6. package/dist/compliance/classify.js +1 -0
  7. package/dist/compliance/impact.d.ts +3 -1
  8. package/dist/compliance/impact.js +41 -13
  9. package/dist/compliance/types.d.ts +6 -0
  10. package/dist/compliance-verification-matrix/build.d.ts +17 -0
  11. package/dist/compliance-verification-matrix/build.js +65 -0
  12. package/dist/compliance-verification-matrix/csv.d.ts +6 -0
  13. package/dist/compliance-verification-matrix/csv.js +41 -0
  14. package/dist/compliance-verification-matrix/index.d.ts +3 -0
  15. package/dist/compliance-verification-matrix/index.js +2 -0
  16. package/dist/compliance-verification-matrix/types.d.ts +75 -0
  17. package/dist/compliance-verification-matrix/types.js +4 -0
  18. package/dist/fgca/action-change-ids.d.ts +7 -0
  19. package/dist/fgca/action-change-ids.js +11 -0
  20. package/dist/fgca/parse-canonical.d.ts +17 -3
  21. package/dist/fgca/parse-canonical.js +44 -16
  22. package/dist/fgca/resolver.d.ts +6 -2
  23. package/dist/fgca/resolver.js +28 -6
  24. package/dist/fgca/validate.d.ts +2 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1 -0
  27. package/dist/process-blueprint/index.d.ts +3 -1
  28. package/dist/process-blueprint/index.js +1 -0
  29. package/dist/process-blueprint/layout.js +33 -29
  30. package/dist/process-blueprint/resolve-columns.d.ts +52 -0
  31. package/dist/process-blueprint/resolve-columns.js +138 -0
  32. package/dist/process-blueprint/types.d.ts +23 -5
  33. package/dist/process-blueprint/validate.d.ts +19 -1
  34. package/dist/process-blueprint/validate.js +73 -21
  35. package/dist/repo-validate/check-process-parent.d.ts +8 -0
  36. package/dist/repo-validate/check-process-parent.js +172 -0
  37. package/dist/repo-validate/index.d.ts +1 -0
  38. package/dist/repo-validate/index.js +1 -0
  39. package/dist/repo-validate/validate-repo.js +3 -0
  40. package/dist/webview/entry.js +26 -4
  41. package/dist/webview/render-fgca.d.ts +2 -0
  42. package/dist/webview/render-fgca.js +9 -3
  43. package/dist/webview/render-process.d.ts +19 -1
  44. package/dist/webview/render-process.js +75 -42
  45. package/package.json +3 -3
  46. package/src/activities/resolver.ts +40 -20
  47. package/src/activities/validate.ts +66 -4
  48. package/src/canon-resolver-utils.ts +19 -0
  49. package/src/compliance/classify.ts +1 -0
  50. package/src/compliance/impact.ts +41 -12
  51. package/src/compliance/types.ts +6 -0
  52. package/src/compliance-verification-matrix/build.ts +84 -0
  53. package/src/compliance-verification-matrix/csv.ts +47 -0
  54. package/src/compliance-verification-matrix/index.ts +7 -0
  55. package/src/compliance-verification-matrix/types.ts +85 -0
  56. package/src/fgca/action-change-ids.ts +13 -0
  57. package/src/fgca/parse-canonical.ts +56 -15
  58. package/src/fgca/resolver.ts +39 -6
  59. package/src/fgca/validate.ts +2 -0
  60. package/src/index.ts +1 -0
  61. package/src/process-blueprint/index.ts +11 -0
  62. package/src/process-blueprint/layout.ts +48 -29
  63. package/src/process-blueprint/resolve-columns.ts +147 -0
  64. package/src/process-blueprint/types.ts +19 -5
  65. package/src/process-blueprint/validate.ts +107 -21
  66. package/src/repo-validate/check-process-parent.ts +184 -0
  67. package/src/repo-validate/index.ts +5 -0
  68. package/src/repo-validate/validate-repo.ts +3 -0
  69. package/src/webview/entry.ts +26 -4
  70. package/src/webview/render-fgca.ts +13 -2
  71. package/src/webview/render-process.ts +94 -49
@@ -1,3 +1,8 @@
1
1
  export { validateRepoModel } from './validate-repo.js';
2
2
  export { resolveRepoModel } from './resolve-model.js';
3
+ export {
4
+ checkProcessParentSemantics,
5
+ collectInEffectProcessParentEdges,
6
+ todayIsoDate,
7
+ } from './check-process-parent.js';
3
8
  export type { RepoDoc, RepoFinding, RepoModelInput, ResolvedElementRecord, ResolvedRelationRecord, ResolvedRepoModel } from './types.js';
@@ -31,6 +31,7 @@ import { checkStrategyChainSemantics } from './check-strategy-chain.js';
31
31
  import { checkElementHygiene } from './check-element-hygiene.js';
32
32
  import { checkVersionedAttributes } from './check-versioned-attributes.js';
33
33
  import { checkCandidateFields } from './check-candidate-fields.js';
34
+ import { checkProcessParentSemantics } from './check-process-parent.js';
34
35
  import type { RepoDoc, RepoFinding, RepoModelInput } from './types.js';
35
36
 
36
37
  const PScope: RepoFinding['scope'] = 'repo';
@@ -161,6 +162,7 @@ function checkReferentialIntegrity(input: RepoModelInput, findings: RepoFinding[
161
162
  * realizes — BUSINESS_SERVICE → CAPABILITY (25-business-services.md)
162
163
  * hosts — NODE → TECHNOLOGY_SERVICE (25-nodes.md / 26-technology-services.md)
163
164
  * uses — APPLICATION → TECHNOLOGY_SERVICE (26-technology-services.md)
165
+ * process_parent — PROCESS → PROCESS (17-relations.md §3; REL-007/008)
164
166
  *
165
167
  * Implemented element checks:
166
168
  * TSVC-003 — TECHNOLOGY_SERVICE.node must resolve to NODE notation
@@ -423,6 +425,7 @@ export function validateRepoModel(input: RepoModelInput): RepoFinding[] {
423
425
  checkAtomicity(input, findings);
424
426
  checkReferentialIntegrity(input, findings);
425
427
  checkLayerSemantics(input, findings);
428
+ checkProcessParentSemantics(input, findings);
426
429
  checkPolicy(input, findings);
427
430
  // Phase 7 — strategy-chain semantic rules ported from DSM's Go Validate*
428
431
  // functions (GOALS-010, ACT-006..009, FGCA-008..011).
@@ -187,22 +187,44 @@ function dispatchValidate(kind: NotationKind, doc: unknown): RenderResult {
187
187
  return r;
188
188
  }
189
189
  case 'dgca': {
190
- const v = parseCanonicalFGCA(doc);
190
+ const meta = (doc ?? {}) as Record<string, unknown>;
191
+ const outOfScopeGoalIds = meta['__outOfScopeGoalIds'] instanceof Set
192
+ ? (meta['__outOfScopeGoalIds'] as Set<string>)
193
+ : Array.isArray(meta['__outOfScopeGoalIds'])
194
+ ? new Set(meta['__outOfScopeGoalIds'] as string[])
195
+ : undefined;
196
+ const outOfScopeFactorIds = meta['__outOfScopeFactorIds'] instanceof Set
197
+ ? (meta['__outOfScopeFactorIds'] as Set<string>)
198
+ : Array.isArray(meta['__outOfScopeFactorIds'])
199
+ ? new Set(meta['__outOfScopeFactorIds'] as string[])
200
+ : undefined;
201
+ const v = parseCanonicalFGCA(doc, outOfScopeGoalIds, outOfScopeFactorIds);
191
202
  const r = emptyResult(kind, v.valid ? 'ok' : 'error');
192
203
  r.errors.push(...v.errors);
193
204
  r.warnings.push(...v.warnings);
194
205
  if (v.valid && v.parsed) {
195
- r.svg = renderFgcaSvg(v.parsed, { variant: 'dgca' });
206
+ r.svg = renderFgcaSvg(v.parsed, { variant: 'dgca', scopeCaption: v.scopeCaption });
196
207
  }
197
208
  return r;
198
209
  }
199
210
  case 'dga': {
200
- const v = parseCanonicalFGA(doc);
211
+ const meta = (doc ?? {}) as Record<string, unknown>;
212
+ const outOfScopeGoalIds = meta['__outOfScopeGoalIds'] instanceof Set
213
+ ? (meta['__outOfScopeGoalIds'] as Set<string>)
214
+ : Array.isArray(meta['__outOfScopeGoalIds'])
215
+ ? new Set(meta['__outOfScopeGoalIds'] as string[])
216
+ : undefined;
217
+ const outOfScopeFactorIds = meta['__outOfScopeFactorIds'] instanceof Set
218
+ ? (meta['__outOfScopeFactorIds'] as Set<string>)
219
+ : Array.isArray(meta['__outOfScopeFactorIds'])
220
+ ? new Set(meta['__outOfScopeFactorIds'] as string[])
221
+ : undefined;
222
+ const v = parseCanonicalFGA(doc, outOfScopeGoalIds, outOfScopeFactorIds);
201
223
  const r = emptyResult(kind, v.valid ? 'ok' : 'error');
202
224
  r.errors.push(...v.errors);
203
225
  r.warnings.push(...v.warnings);
204
226
  if (v.valid && v.parsed) {
205
- r.svg = renderFgcaSvg(v.parsed, { variant: 'dga' });
227
+ r.svg = renderFgcaSvg(v.parsed, { variant: 'dga', scopeCaption: v.scopeCaption });
206
228
  }
207
229
  return r;
208
230
  }
@@ -110,6 +110,8 @@ export interface RenderFgcaOptions {
110
110
  entryCurvature?: number;
111
111
  nodeSizePreset?: NodeSizePreset;
112
112
  layoutOptions?: Parameters<typeof layoutFGCAPreview>[1];
113
+ /** Show scope caption when goal-scoped projection omits goals that reference out-of-scope goals. */
114
+ scopeCaption?: boolean;
113
115
  }
114
116
 
115
117
  export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): string {
@@ -120,8 +122,9 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
120
122
  entryCurvature,
121
123
  nodeSizePreset = 'normal',
122
124
  layoutOptions,
125
+ scopeCaption = false,
123
126
  } = options;
124
- const hideChanges = variant === 'dga';
127
+ const hideChanges = variant === 'dga' || doc.hideChanges === true;
125
128
  const nodeSize = resolveDgcaNodeSize(parseNodeSizePreset(nodeSizePreset));
126
129
 
127
130
  const { nodes, edges, columns, width, height } = layoutFGCAPreview(doc, {
@@ -144,9 +147,16 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
144
147
  ? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(title)}</text>`
145
148
  : '';
146
149
 
150
+ const captionSvg = scopeCaption
151
+ ? `<text class="text-caption" x="${PAD}" y="${height + 16}">${escXml('This diagram is shown for the selected goal scope. Actions on it may also serve goals outside that scope.')}</text>`
152
+ : '';
153
+
147
154
  const embedCss = generateSvgEmbedCss('transitrix');
148
155
 
149
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
156
+ // Calculate final height including caption if present
157
+ const finalHeight = scopeCaption ? height + 24 : height;
158
+
159
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${finalHeight}" viewBox="0 0 ${width} ${finalHeight}">
150
160
  <style>${embedCss}</style>
151
161
  <defs>
152
162
  <marker id="arrow" markerWidth="8" markerHeight="8" refX="8" refY="3" orient="auto">
@@ -155,5 +165,6 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
155
165
  </defs>
156
166
  ${titleSvg}
157
167
  ${body}
168
+ ${captionSvg}
158
169
  </svg>`;
159
170
  }
@@ -75,11 +75,48 @@ export interface ProcessDiagramLayout {
75
75
  associations: ProcessAssociation[];
76
76
  }
77
77
 
78
+ export interface BpmnTypography {
79
+ taskFontPx: number;
80
+ headerFontPx: number;
81
+ labelFontPx: number;
82
+ taskChars: number;
83
+ taskLineH: number;
84
+ labelChars: number;
85
+ labelLineH: number;
86
+ headerCharW: number;
87
+ }
88
+
89
+ /** Interactive-preview / default-export type sizes. */
90
+ export const DEFAULT_BPMN_TYPOGRAPHY: BpmnTypography = {
91
+ taskFontPx: 11,
92
+ headerFontPx: 11,
93
+ labelFontPx: 10,
94
+ taskChars: 14,
95
+ taskLineH: 14,
96
+ labelChars: 18,
97
+ labelLineH: 13,
98
+ headerCharW: 6.5,
99
+ };
100
+
101
+ /** Presentation-export type sizes (20 px floor, wrap tuned to default node boxes). */
102
+ export const PRESENTATION_BPMN_TYPOGRAPHY: BpmnTypography = {
103
+ taskFontPx: 20,
104
+ headerFontPx: 20,
105
+ labelFontPx: 20,
106
+ taskChars: 9,
107
+ taskLineH: 22,
108
+ labelChars: 14,
109
+ labelLineH: 22,
110
+ headerCharW: 11.5,
111
+ };
112
+
78
113
  export interface RenderProcessOptions {
79
114
  /** Raw SVG injected after `<style>` — a title line or full title block. */
80
115
  title?: string;
81
116
  /** Extra vertical space (px) reserved above the pool for a title block. */
82
117
  topInset?: number;
118
+ /** Label metrics. Omit for the interactive-preview defaults. */
119
+ typography?: BpmnTypography;
83
120
  }
84
121
 
85
122
  // ---------------------------------------------------------------------------
@@ -92,49 +129,52 @@ const LANE_LABEL_BAND = 44;
92
129
  /** End margin (px) along the header height axis for rotated pool/lane captions. */
93
130
  const HEADER_LABEL_AXIS_PAD = 32;
94
131
 
95
- /** Estimated horizontal advance (px) per character at the header label font size. */
96
- const HEADER_LABEL_CHAR_W = 6.5;
97
-
98
- /** Maximum characters per wrapped line inside a task box. */
99
- const TASK_CHARS = 14;
132
+ /** Active wrap/font metrics for the current render call (sync, restored after). */
133
+ let typo: BpmnTypography = DEFAULT_BPMN_TYPOGRAPHY;
100
134
 
101
- /** Line height for wrapped task-name text (px). */
102
- const TASK_LINE_H = 14;
103
-
104
- /** Maximum characters per line for below-element labels (events, gateways). */
105
- const LABEL_CHARS = 18;
106
-
107
- /** Line height for multi-line below-element labels (px). */
108
- const LABEL_LINE_H = 13;
135
+ function runWithTypography<T>(t: BpmnTypography, fn: () => T): T {
136
+ const prev = typo;
137
+ typo = t;
138
+ try {
139
+ return fn();
140
+ } finally {
141
+ typo = prev;
142
+ }
143
+ }
109
144
 
110
145
  // ---------------------------------------------------------------------------
111
146
  // BPMN-specific CSS — no font-style:italic per CLAUDE.md design rule
112
147
  // ---------------------------------------------------------------------------
113
148
 
114
- export const BPMN_PROCESS_CSS = `
149
+ export function bpmnProcessCss(t: BpmnTypography = DEFAULT_BPMN_TYPOGRAPHY): string {
150
+ return `
115
151
  .bpmn-pool { fill: var(--ts-bg-surface,#f8fafc); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
116
152
  .bpmn-pool-name { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
117
153
  .bpmn-lane { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
118
154
  .bpmn-lane-header { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
119
- .bpmn-pool-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 11px; font-weight: 700; }
120
- .bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 11px; font-weight: 600; }
155
+ .bpmn-pool-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.headerFontPx}px; font-weight: 700; }
156
+ .bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.headerFontPx}px; font-weight: 600; }
121
157
  .bpmn-task { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
122
- .bpmn-task-name { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 11px; text-anchor: middle; }
158
+ .bpmn-task-name { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.taskFontPx}px; text-anchor: middle; }
123
159
  .bpmn-event { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); }
124
160
  .bpmn-event-start { stroke-width: 1.5; }
125
161
  .bpmn-event-end { stroke-width: 4; }
126
- .bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 10px; text-anchor: middle; }
162
+ .bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.labelFontPx}px; text-anchor: middle; }
127
163
  .bpmn-gateway { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
128
164
  .bpmn-gateway-marker { stroke: var(--ts-node-stroke,#004d67); stroke-width: 2.5; stroke-linecap: round; fill: none; }
129
- .bpmn-gateway-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: 10px; text-anchor: middle; }
165
+ .bpmn-gateway-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.labelFontPx}px; text-anchor: middle; }
130
166
  .bpmn-seq-flow { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; }
131
167
  .bpmn-default-mark { stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; }
132
168
  .bpmn-assoc { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1; stroke-dasharray: 4 2; }
133
169
  .bpmn-data-obj { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }
134
- .bpmn-data-obj-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }
170
+ .bpmn-data-obj-label { fill: var(--ts-text-secondary,#516970); font-size: ${t.labelFontPx}px; text-anchor: middle; }
135
171
  .bpmn-event-intermediate { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }
136
172
  .bpmn-event-icon { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
137
173
  `;
174
+ }
175
+
176
+ /** Default-preview CSS (11 px / 10 px). Presentation export builds its own via `bpmnProcessCss`. */
177
+ export const BPMN_PROCESS_CSS = bpmnProcessCss(DEFAULT_BPMN_TYPOGRAPHY);
138
178
 
139
179
  // ---------------------------------------------------------------------------
140
180
  // Internal helpers
@@ -153,54 +193,56 @@ function wordTruncate(text: string, maxChars: number): string {
153
193
  }
154
194
 
155
195
  function fitRotatedHeaderText(name: string, spanPx: number): string {
156
- const maxChars = Math.max(4, Math.floor(spanPx / HEADER_LABEL_CHAR_W));
196
+ const maxChars = Math.max(4, Math.floor(spanPx / typo.headerCharW));
157
197
  return wordTruncate(name, maxChars);
158
198
  }
159
199
 
160
200
  function wrapTaskName(name: string): string[] {
161
201
  if (!name) return [];
202
+ const limit = typo.taskChars;
162
203
  const words = name.split(/\s+/);
163
204
  const lines: string[] = [];
164
205
  let cur = '';
165
206
  for (const w of words) {
166
- const token = w.length > TASK_CHARS ? w.slice(0, TASK_CHARS - 1) + '…' : w;
207
+ const token = w.length > limit ? w.slice(0, limit - 1) + '…' : w;
167
208
  if (!cur) {
168
209
  cur = token;
169
- } else if (cur.length + 1 + token.length <= TASK_CHARS) {
210
+ } else if (cur.length + 1 + token.length <= limit) {
170
211
  cur += ' ' + token;
171
212
  } else {
172
213
  lines.push(cur);
173
214
  if (lines.length === 3) {
174
- if (lines[2].length > TASK_CHARS - 1) lines[2] = lines[2].slice(0, TASK_CHARS - 1) + '…';
215
+ if (lines[2].length > limit - 1) lines[2] = lines[2].slice(0, limit - 1) + '…';
175
216
  return lines;
176
217
  }
177
218
  cur = token;
178
219
  }
179
220
  }
180
221
  if (cur && lines.length < 3) lines.push(cur);
181
- if (lines.length === 0) lines.push(name.slice(0, TASK_CHARS));
222
+ if (lines.length === 0) lines.push(name.slice(0, limit));
182
223
  return lines;
183
224
  }
184
225
 
185
226
  function taskNameSvg(name: string | undefined, cx: number, cy: number): string {
186
227
  if (!name) return '';
187
228
  const lines = wrapTaskName(name);
188
- const totalH = lines.length * TASK_LINE_H;
189
- const startY = cy - totalH / 2 + TASK_LINE_H * 0.8;
229
+ const totalH = lines.length * typo.taskLineH;
230
+ const startY = cy - totalH / 2 + typo.taskLineH * 0.8;
190
231
  const tspans = lines
191
- .map((ln, i) => `<tspan x="${r(cx)}" y="${r(startY + i * TASK_LINE_H)}">${escXml(ln)}</tspan>`)
232
+ .map((ln, i) => `<tspan x="${r(cx)}" y="${r(startY + i * typo.taskLineH)}">${escXml(ln)}</tspan>`)
192
233
  .join('');
193
234
  return `<text class="bpmn-task-name">${tspans}</text>`;
194
235
  }
195
236
 
196
237
  function wrapBelowLabel(name: string): string[] {
238
+ const limit = typo.labelChars;
197
239
  const words = name.split(/\s+/).filter(Boolean);
198
240
  const allLines: string[] = [];
199
241
  let cur = '';
200
242
  for (const word of words) {
201
- const tok = word.length > LABEL_CHARS ? word.slice(0, LABEL_CHARS - 1) + '…' : word;
243
+ const tok = word.length > limit ? word.slice(0, limit - 1) + '…' : word;
202
244
  const test = cur ? `${cur} ${tok}` : tok;
203
- if (test.length <= LABEL_CHARS) {
245
+ if (test.length <= limit) {
204
246
  cur = test;
205
247
  } else {
206
248
  if (cur) allLines.push(cur);
@@ -208,11 +250,11 @@ function wrapBelowLabel(name: string): string[] {
208
250
  }
209
251
  }
210
252
  if (cur) allLines.push(cur);
211
- if (allLines.length === 0) return [name.slice(0, LABEL_CHARS)];
253
+ if (allLines.length === 0) return [name.slice(0, limit)];
212
254
  if (allLines.length <= 3) return allLines;
213
255
  const lines = allLines.slice(0, 3);
214
256
  const last = lines[2];
215
- lines[2] = last.length + 1 <= LABEL_CHARS ? last + '…' : last.slice(0, LABEL_CHARS - 1) + '…';
257
+ lines[2] = last.length + 1 <= limit ? last + '…' : last.slice(0, limit - 1) + '…';
216
258
  return lines;
217
259
  }
218
260
 
@@ -226,7 +268,7 @@ function belowLabelSvg(name: string | undefined, cls: string, cx: number, belowY
226
268
  return `<text class="${cls}" x="${r(textX)}" y="${startY}">${escXml(lines[0])}</text>`;
227
269
  }
228
270
  const tspans = lines
229
- .map((ln, i) => `<tspan x="${r(textX)}" dy="${i === 0 ? 0 : LABEL_LINE_H}">${escXml(ln)}</tspan>`)
271
+ .map((ln, i) => `<tspan x="${r(textX)}" dy="${i === 0 ? 0 : typo.labelLineH}">${escXml(ln)}</tspan>`)
230
272
  .join('');
231
273
  return `<text class="${cls}" x="${r(textX)}" y="${startY}">${tspans}</text>`;
232
274
  }
@@ -488,20 +530,23 @@ export function renderProcessLayoutSvg(
488
530
  options: RenderProcessOptions = {},
489
531
  ): string {
490
532
  const { title = '', topInset = title ? 32 : 0 } = options;
491
- const pb = layout.poolBounds;
492
- const PAD = 16;
493
-
494
- const svgW = r(pb.x + pb.width + PAD);
495
- const svgH = r(pb.y + pb.height + PAD + topInset);
496
-
497
- const embedCss = generateSvgEmbedCss('transitrix') + BPMN_PROCESS_CSS;
498
- const body = renderProcessBody(layout, 0, topInset);
499
-
500
- return (
501
- `<svg xmlns="http://www.w3.org/2000/svg" width="${svgW}" height="${svgH}" viewBox="0 0 ${svgW} ${svgH}">\n` +
502
- `<style>${embedCss}</style>\n` +
503
- (title ? `${title}\n` : '') +
504
- `${body}\n` +
505
- `</svg>`
506
- );
533
+ const t = options.typography ?? DEFAULT_BPMN_TYPOGRAPHY;
534
+ return runWithTypography(t, () => {
535
+ const pb = layout.poolBounds;
536
+ const PAD = 16;
537
+
538
+ const svgW = r(pb.x + pb.width + PAD);
539
+ const svgH = r(pb.y + pb.height + PAD + topInset);
540
+
541
+ const embedCss = generateSvgEmbedCss('transitrix') + bpmnProcessCss(t);
542
+ const body = renderProcessBody(layout, 0, topInset);
543
+
544
+ return (
545
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${svgW}" height="${svgH}" viewBox="0 0 ${svgW} ${svgH}">\n` +
546
+ `<style>${embedCss}</style>\n` +
547
+ (title ? `${title}\n` : '') +
548
+ `${body}\n` +
549
+ `</svg>`
550
+ );
551
+ });
507
552
  }