@zombie-mermaid/svg-renderer 2.2.6 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -8
- package/dist/index.cjs +37 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.js +1436 -893
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/__tests__/render-mermaid-svg.test.ts +64 -0
- package/src/class/renderer.ts +65 -62
- package/src/edge-curves.ts +17 -16
- package/src/er/renderer.ts +50 -49
- package/src/index.ts +222 -5
- package/src/layout-engine/from-elk.ts +80 -5
- package/src/registry.ts +351 -0
- package/src/renderer.ts +190 -189
- package/src/sequence/renderer.ts +66 -65
- package/src/layout.ts +0 -8
package/src/registry.ts
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// zombie-mermaid — SVG diagram-type registry
|
|
3
|
+
//
|
|
4
|
+
// A per-type registration table `renderMermaidSVG` (./index.ts) looks up
|
|
5
|
+
// instead of hand-maintaining its own `switch (diagramType)` over the same
|
|
6
|
+
// `DiagramType`. Moved here from the umbrella's `src/diagram-registry.ts`
|
|
7
|
+
// under issue #1111, for parity with `@zombie-mermaid/ascii-renderer`'s own
|
|
8
|
+
// `registry.ts` — see docs/decisions/diagram-type-registry-partial.md for
|
|
9
|
+
// why the SVG and ASCII halves stayed two separate tables rather than one
|
|
10
|
+
// shared `DiagramModule` with both a `renderSvg` and a `renderAscii` method
|
|
11
|
+
// (short version: one table made this module import out of
|
|
12
|
+
// `packages/ascii-renderer/`, while that package's `index.ts` imported this
|
|
13
|
+
// one back, a cycle that blocked the monorepo split and dragged `elkjs`
|
|
14
|
+
// into `dist/ascii.js`).
|
|
15
|
+
//
|
|
16
|
+
// Every diagram type is registered here, including 'flowchart' — see the
|
|
17
|
+
// decision doc for why it was the last holdout (its ASCII path had no
|
|
18
|
+
// per-type wrapper function to slot in until #745 extracted
|
|
19
|
+
// `renderFlowchartAscii`, and its SVG path carries `%%{init: ...}%%`
|
|
20
|
+
// directive handling no other type has — see `flowchartModule.layoutForSvg`
|
|
21
|
+
// below for how that's folded in without growing `SvgRenderContext`). The
|
|
22
|
+
// front door (`renderMermaidSVG` in ./index.ts) has no fallback switch left;
|
|
23
|
+
// every `DiagramType` is a hit here.
|
|
24
|
+
//
|
|
25
|
+
// `packages/core/src/diagram-type.ts` (the `DiagramType` union + `detectDiagramType`)
|
|
26
|
+
// stays exactly as-is and is what the front door uses to key into this
|
|
27
|
+
// table — this module doesn't touch detection.
|
|
28
|
+
//
|
|
29
|
+
// `parseMermaid` (flowchart/state parsing) reaches out to the umbrella's
|
|
30
|
+
// `../../../src/parser.ts` rather than a `@zombie-mermaid/*` package
|
|
31
|
+
// specifier — flowchart/state parsing was deliberately never extracted into
|
|
32
|
+
// `@zombie-mermaid/mermaid-parser` (see that package's scoping doc), the same
|
|
33
|
+
// pre-existing boundary call `packages/ascii-renderer/src/flowchart.ts`
|
|
34
|
+
// already reaches across for the ASCII side. `vite.config.ts` bundles that
|
|
35
|
+
// file straight into this package's own dist, exactly as it already does
|
|
36
|
+
// for `ascii-renderer` — see that file's header comment.
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
import type {
|
|
40
|
+
DiagramType,
|
|
41
|
+
RenderOptions,
|
|
42
|
+
DiagramColors,
|
|
43
|
+
SvgEmitOptions,
|
|
44
|
+
MermaidGraph,
|
|
45
|
+
PositionedGraph,
|
|
46
|
+
CurveStyle,
|
|
47
|
+
Statement,
|
|
48
|
+
} from '@zombie-mermaid/core'
|
|
49
|
+
import type { FontSizes } from './styles.ts'
|
|
50
|
+
import { withDirectionOverride } from '@zombie-mermaid/core'
|
|
51
|
+
|
|
52
|
+
import {
|
|
53
|
+
parseXYChart,
|
|
54
|
+
parseErDiagram,
|
|
55
|
+
parseSequenceDiagram,
|
|
56
|
+
parseClassDiagram,
|
|
57
|
+
} from '@zombie-mermaid/mermaid-parser'
|
|
58
|
+
import type {
|
|
59
|
+
XYChart,
|
|
60
|
+
PositionedXYChart,
|
|
61
|
+
ErDiagram,
|
|
62
|
+
PositionedErDiagram,
|
|
63
|
+
SequenceDiagram,
|
|
64
|
+
PositionedSequenceDiagram,
|
|
65
|
+
ClassDiagram,
|
|
66
|
+
PositionedClassDiagram,
|
|
67
|
+
} from '@zombie-mermaid/mermaid-parser'
|
|
68
|
+
import { layoutXYChart } from './xychart/layout.ts'
|
|
69
|
+
import { renderXYChartSvg } from './xychart/renderer.ts'
|
|
70
|
+
import { layoutErDiagramSync } from './er/layout.ts'
|
|
71
|
+
import { renderErSvg } from './er/renderer.ts'
|
|
72
|
+
import { layoutSequenceDiagram } from './sequence/layout.ts'
|
|
73
|
+
import { renderSequenceSvg } from './sequence/renderer.ts'
|
|
74
|
+
import { layoutClassDiagramSync } from './class/layout.ts'
|
|
75
|
+
import { renderClassSvg } from './class/renderer.ts'
|
|
76
|
+
import { layoutGraphSync } from './layout-engine.ts'
|
|
77
|
+
import { renderSvg as renderFlowchartSvg } from './renderer.ts'
|
|
78
|
+
import { parseMermaid } from '../../../src/parser.ts'
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parameters shared by every per-type SVG renderer today, factored out of
|
|
82
|
+
* the positional-argument lists that otherwise differ type to type (the
|
|
83
|
+
* inconsistency issue #533 flags — confirmed by reading all five renderer
|
|
84
|
+
* signatures: `embedSource`/`title`/`decorative`/`emit` shift position
|
|
85
|
+
* across `renderSvg`, `renderSequenceSvg`, `renderClassSvg`, `renderErSvg`,
|
|
86
|
+
* `renderXYChartSvg`, and `renderErSvg` drops `linksEnabled` entirely).
|
|
87
|
+
*
|
|
88
|
+
* Fields only one or two types need (`curve`, `animationEnabled`,
|
|
89
|
+
* `linksEnabled`, `interactive`) are deliberately left OUT of this shared
|
|
90
|
+
* shape rather than grown in for every exception — each type's own
|
|
91
|
+
* `renderSvg` adapter below derives those straight from `options`, exactly
|
|
92
|
+
* as each existing `renderMermaidSVGRaw` switch case already did.
|
|
93
|
+
*/
|
|
94
|
+
export interface SvgRenderContext {
|
|
95
|
+
colors: DiagramColors
|
|
96
|
+
font: string
|
|
97
|
+
transparent: boolean
|
|
98
|
+
fontSizes: FontSizes
|
|
99
|
+
embedSource?: string
|
|
100
|
+
title?: string
|
|
101
|
+
decorative?: boolean
|
|
102
|
+
emit: SvgEmitOptions
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* One diagram type's full registration.
|
|
107
|
+
*
|
|
108
|
+
* `layoutForSvg` is deliberately SVG-only — it is NOT shared with the ASCII
|
|
109
|
+
* side, unlike the issue's original `{ detect, parse, layout, renderSvg,
|
|
110
|
+
* renderAscii }` sketch. Reading every ASCII per-type module confirmed why a
|
|
111
|
+
* single shared `layout` step would be fiction, not simplification, for this
|
|
112
|
+
* codebase: SVG layout produces pixel coordinates (`PositionedXYChart`,
|
|
113
|
+
* `PositionedErDiagram`, …), while every ASCII renderer does its own,
|
|
114
|
+
* unrelated grid/canvas layout internally — see e.g.
|
|
115
|
+
* `packages/ascii-renderer/src/xychart.ts`'s file header: "Uses the parsed
|
|
116
|
+
* XYChart type directly (not PositionedXYChart) since pixel coordinates
|
|
117
|
+
* don't map to character grids." `parse` genuinely is shared in the sense
|
|
118
|
+
* that both front doors call the same `parseXYChart`/`parseErDiagram`, but
|
|
119
|
+
* each ASCII renderer reruns that parse itself from raw text — which is why
|
|
120
|
+
* the ASCII entries live in `packages/ascii-renderer/src/registry.ts` as
|
|
121
|
+
* plain `(text, …) => string` functions instead of a `renderAscii` method on
|
|
122
|
+
* this interface.
|
|
123
|
+
*
|
|
124
|
+
* `parse` takes both `lines` (the pre-split statement list from
|
|
125
|
+
* `splitStatements(decoded)`, computed once by the front door — what every
|
|
126
|
+
* already-registered type's parser wants) and `text` (the raw, un-split
|
|
127
|
+
* source `flowchartModule` below needs instead): `parseMermaid`'s
|
|
128
|
+
* `%%{init: ...}%%` directive extraction reads raw, un-commented lines that
|
|
129
|
+
* `splitStatements` has already discarded by the time `lines` exists, and
|
|
130
|
+
* its multi-line-statement continuation merging needs each statement's
|
|
131
|
+
* *originating physical line* grouping, which `splitStatements`'s flattened
|
|
132
|
+
* array has already lost. `xychartModule`/`erModule`/`sequenceModule`/
|
|
133
|
+
* `classModule` all ignore the second parameter — JS/TS functions may take
|
|
134
|
+
* fewer parameters than their declared type allows, so `parse: parseXYChart`
|
|
135
|
+
* (etc.) is unchanged from before this parameter was added.
|
|
136
|
+
*/
|
|
137
|
+
export interface DiagramModule<TDiagram = unknown, TPositioned = unknown> {
|
|
138
|
+
readonly type: DiagramType
|
|
139
|
+
parse(lines: Statement[], text: string): TDiagram
|
|
140
|
+
layoutForSvg(diagram: TDiagram, options: RenderOptions): TPositioned
|
|
141
|
+
renderSvg(
|
|
142
|
+
positioned: TPositioned,
|
|
143
|
+
ctx: SvgRenderContext,
|
|
144
|
+
options: RenderOptions,
|
|
145
|
+
): string
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const xychartModule: DiagramModule<XYChart, PositionedXYChart> = {
|
|
149
|
+
type: 'xychart',
|
|
150
|
+
parse: parseXYChart,
|
|
151
|
+
layoutForSvg: layoutXYChart,
|
|
152
|
+
renderSvg(positioned, ctx, options) {
|
|
153
|
+
// Mirrors resolveXYChartInteractive() in the umbrella's src/index.ts
|
|
154
|
+
// exactly — `interactivity` wins when set, otherwise the deprecated
|
|
155
|
+
// `interactive` boolean keeps controlling this as before.
|
|
156
|
+
const interactive =
|
|
157
|
+
options.interactivity !== undefined
|
|
158
|
+
? options.interactivity === 'full'
|
|
159
|
+
: (options.interactive ?? false)
|
|
160
|
+
return renderXYChartSvg(
|
|
161
|
+
positioned,
|
|
162
|
+
ctx.colors,
|
|
163
|
+
ctx.font,
|
|
164
|
+
ctx.transparent,
|
|
165
|
+
interactive,
|
|
166
|
+
ctx.embedSource,
|
|
167
|
+
ctx.title,
|
|
168
|
+
ctx.decorative,
|
|
169
|
+
ctx.emit,
|
|
170
|
+
)
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const sequenceModule: DiagramModule<
|
|
175
|
+
SequenceDiagram,
|
|
176
|
+
PositionedSequenceDiagram
|
|
177
|
+
> = {
|
|
178
|
+
type: 'sequence',
|
|
179
|
+
parse: parseSequenceDiagram,
|
|
180
|
+
layoutForSvg: layoutSequenceDiagram,
|
|
181
|
+
renderSvg(positioned, ctx) {
|
|
182
|
+
return renderSequenceSvg(
|
|
183
|
+
positioned,
|
|
184
|
+
ctx.colors,
|
|
185
|
+
ctx.font,
|
|
186
|
+
ctx.transparent,
|
|
187
|
+
ctx.fontSizes,
|
|
188
|
+
ctx.embedSource,
|
|
189
|
+
ctx.title,
|
|
190
|
+
ctx.decorative,
|
|
191
|
+
ctx.emit,
|
|
192
|
+
)
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Mirrors `resolveLinksEnabled()` in the umbrella's src/index.ts exactly
|
|
198
|
+
* (`interactivity` defaults unset to `'static'`; only `'none'` turns links
|
|
199
|
+
* off) — duplicated here rather than imported since that helper is private
|
|
200
|
+
* to src/index.ts, which now imports this module (importing it back would
|
|
201
|
+
* cycle).
|
|
202
|
+
*/
|
|
203
|
+
function resolveLinksEnabled(options: RenderOptions): boolean {
|
|
204
|
+
const interactivity = options.interactivity ?? 'static'
|
|
205
|
+
return interactivity !== 'none'
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Mirrors `resolveAnimationEnabled()` in the umbrella's src/index.ts exactly
|
|
210
|
+
* (`interactivity` defaults unset to `'static'`; only `'full'` turns
|
|
211
|
+
* animation on) — duplicated here for the same import-direction reason
|
|
212
|
+
* `resolveLinksEnabled` above is.
|
|
213
|
+
*/
|
|
214
|
+
function resolveAnimationEnabled(options: RenderOptions): boolean {
|
|
215
|
+
const interactivity = options.interactivity ?? 'static'
|
|
216
|
+
return interactivity === 'full'
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const classModule: DiagramModule<ClassDiagram, PositionedClassDiagram> = {
|
|
220
|
+
type: 'class',
|
|
221
|
+
parse: parseClassDiagram,
|
|
222
|
+
layoutForSvg: layoutClassDiagramSync,
|
|
223
|
+
renderSvg(positioned, ctx, options) {
|
|
224
|
+
return renderClassSvg(
|
|
225
|
+
positioned,
|
|
226
|
+
ctx.colors,
|
|
227
|
+
ctx.font,
|
|
228
|
+
ctx.transparent,
|
|
229
|
+
ctx.fontSizes,
|
|
230
|
+
ctx.embedSource,
|
|
231
|
+
ctx.title,
|
|
232
|
+
ctx.decorative,
|
|
233
|
+
resolveLinksEnabled(options),
|
|
234
|
+
ctx.emit,
|
|
235
|
+
)
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const erModule: DiagramModule<ErDiagram, PositionedErDiagram> = {
|
|
240
|
+
type: 'er',
|
|
241
|
+
parse: parseErDiagram,
|
|
242
|
+
// `options.direction` replaces the diagram's own top-level `direction`
|
|
243
|
+
// line, if any, before layout — same order as the umbrella's original
|
|
244
|
+
// 'er' case: parse, then withDirectionOverride, then layout.
|
|
245
|
+
layoutForSvg(diagram, options) {
|
|
246
|
+
return layoutErDiagramSync(
|
|
247
|
+
withDirectionOverride(diagram, options.direction),
|
|
248
|
+
options,
|
|
249
|
+
)
|
|
250
|
+
},
|
|
251
|
+
renderSvg(positioned, ctx) {
|
|
252
|
+
return renderErSvg(
|
|
253
|
+
positioned,
|
|
254
|
+
ctx.colors,
|
|
255
|
+
ctx.font,
|
|
256
|
+
ctx.transparent,
|
|
257
|
+
ctx.fontSizes,
|
|
258
|
+
ctx.embedSource,
|
|
259
|
+
ctx.title,
|
|
260
|
+
ctx.decorative,
|
|
261
|
+
ctx.emit,
|
|
262
|
+
)
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* `renderSvg`'s (the low-level flowchart/state SVG emitter, imported above
|
|
268
|
+
* as `renderFlowchartSvg`) `curve` parameter is the one piece of
|
|
269
|
+
* `SvgRenderContext`-adjacent state no other registered type needs: a
|
|
270
|
+
* `%%{init: {"flowchart": {"curve": ...}}}%%` directive on the diagram
|
|
271
|
+
* itself can supply a default, and `options.curve` always wins when set —
|
|
272
|
+
* see `applyInitConfig()` in `packages/core/src/init-directive.ts`, which
|
|
273
|
+
* the umbrella's pre-registry flowchart path used to call directly.
|
|
274
|
+
*
|
|
275
|
+
* That resolution needs the parsed diagram (for `initConfig`) AND the
|
|
276
|
+
* caller's `options` together, and the *result* is only needed later, by
|
|
277
|
+
* `renderSvg` — so rather than teach `SvgRenderContext` a diagram-specific
|
|
278
|
+
* `curve` field (every other field there is genuinely shared across types),
|
|
279
|
+
* `layoutForSvg` below resolves it once and carries it forward on
|
|
280
|
+
* `PositionedFlowchart`, right next to the `PositionedGraph` it was
|
|
281
|
+
* resolved alongside. `animationEnabled`/`linksEnabled` need no such
|
|
282
|
+
* carry-through: both derive from `options` alone via the
|
|
283
|
+
* `resolveAnimationEnabled`/`resolveLinksEnabled` helpers above, exactly
|
|
284
|
+
* like `class`'s `linksEnabled` already does, so `flowchartModule.renderSvg`
|
|
285
|
+
* just calls them directly.
|
|
286
|
+
*/
|
|
287
|
+
export interface PositionedFlowchart {
|
|
288
|
+
graph: PositionedGraph
|
|
289
|
+
curve: CurveStyle
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const flowchartModule: DiagramModule<MermaidGraph, PositionedFlowchart> = {
|
|
293
|
+
type: 'flowchart',
|
|
294
|
+
// Ignores `lines` — see the `parse` doc comment on `DiagramModule` above
|
|
295
|
+
// for why flowchart/state needs the raw `text` instead.
|
|
296
|
+
parse: (_lines, text) => parseMermaid(text),
|
|
297
|
+
layoutForSvg(diagram, options) {
|
|
298
|
+
// Same order as the umbrella's original fallback: direction override,
|
|
299
|
+
// then layout. `options.curve` wins over the diagram's own
|
|
300
|
+
// `%%{init: ...}%%` directive, which wins over the 'linear' default —
|
|
301
|
+
// see the doc comment on `PositionedFlowchart` above.
|
|
302
|
+
const graph = withDirectionOverride(diagram, options.direction)
|
|
303
|
+
return {
|
|
304
|
+
graph: layoutGraphSync(graph, options),
|
|
305
|
+
curve: options.curve ?? diagram.initConfig?.curve ?? 'linear',
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
renderSvg(positioned, ctx, options) {
|
|
309
|
+
return renderFlowchartSvg(
|
|
310
|
+
positioned.graph,
|
|
311
|
+
ctx.colors,
|
|
312
|
+
ctx.font,
|
|
313
|
+
ctx.transparent,
|
|
314
|
+
ctx.fontSizes,
|
|
315
|
+
positioned.curve,
|
|
316
|
+
ctx.embedSource,
|
|
317
|
+
resolveAnimationEnabled(options),
|
|
318
|
+
resolveLinksEnabled(options),
|
|
319
|
+
ctx.title,
|
|
320
|
+
ctx.decorative,
|
|
321
|
+
ctx.emit,
|
|
322
|
+
)
|
|
323
|
+
},
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* The registry proper — every `DiagramType` is looked up here by the SVG
|
|
328
|
+
* front door (`renderMermaidSVG` in ./index.ts), which has no fallback
|
|
329
|
+
* switch left. The ASCII front door's equivalent table is `asciiRegistry`
|
|
330
|
+
* in `packages/ascii-renderer/src/registry.ts`.
|
|
331
|
+
*
|
|
332
|
+
* Typed with `any` type parameters at the map level: each entry's own
|
|
333
|
+
* `TDiagram`/`TPositioned` are only known inside that entry's own closure
|
|
334
|
+
* (see `xychartModule`/`erModule`/`flowchartModule` above, which are fully
|
|
335
|
+
* typed); the map just needs one consistent shape to hold heterogeneous
|
|
336
|
+
* entries in.
|
|
337
|
+
*/
|
|
338
|
+
type AnyDiagramModule = DiagramModule<
|
|
339
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- see comment above; each entry is fully typed at its own definition site.
|
|
340
|
+
any,
|
|
341
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- see comment above; each entry is fully typed at its own definition site.
|
|
342
|
+
any
|
|
343
|
+
>
|
|
344
|
+
|
|
345
|
+
export const diagramRegistry: Record<DiagramType, AnyDiagramModule> = {
|
|
346
|
+
xychart: xychartModule,
|
|
347
|
+
er: erModule,
|
|
348
|
+
sequence: sequenceModule,
|
|
349
|
+
class: classModule,
|
|
350
|
+
flowchart: flowchartModule,
|
|
351
|
+
}
|