@zombie-mermaid/svg-renderer 3.0.0 → 3.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/edge-curves.ts","../src/elk-instance.ts","../src/styles.ts","../src/layout-engine/constants.ts","../src/layout-engine/elk-graph-builder.ts","../src/layout-engine/to-elk.ts","../src/shape-clipping.ts","../src/layout-engine/layer-alignment.ts","../src/layout-engine/edge-bundling.ts","../src/layout-engine/elk-adapter-utils.ts","../src/layout-engine/from-elk.ts","../src/layout-engine.ts","../src/renderer.ts","../src/resolve-colors.ts","../src/class/layout.ts","../src/class/renderer.ts","../src/er/layout.ts","../src/er/renderer.ts","../src/sequence/layout.ts","../src/sequence/renderer.ts","../src/xychart/layout.ts","../src/xychart/renderer.ts","../../../src/parser.ts","../src/registry.ts","../src/index.ts"],"sourcesContent":["// ============================================================================\n// zombie-mermaid — edge path interpolation (Mermaid's `flowchart.curve`)\n//\n// ELK routes an edge as a list of bend points. How those points are joined\n// into a drawn line is a separate, purely presentational choice, which\n// Mermaid exposes as `%%{init: {\"flowchart\": {\"curve\": \"basis\"}}}%%`.\n//\n// Every curve here consumes the same routed points, so switching styles never\n// changes where an edge goes — only how it looks getting there.\n// ============================================================================\n\nimport type { Point, CurveStyle } from '@zombie-mermaid/core'\n\n/** `M x y` for the first point. */\nfunction moveTo(p: Point): string {\n return `M ${p.x} ${p.y}`\n}\n\n/** Straight segments through every point — the historical default. */\nfunction linearPath(points: Point[]): string {\n return (\n moveTo(points[0]!) +\n points\n .slice(1)\n .map((p) => ` L ${p.x} ${p.y}`)\n .join('')\n )\n}\n\n/**\n * Uniform cubic B-spline, matching d3's `curveBasis` (what Mermaid uses for\n * `basis`).\n *\n * A B-spline is *approximating*, not interpolating: it passes through neither\n * the interior points nor, without the endpoint handling below, the ends. The\n * first and last points are therefore emitted explicitly so an edge still\n * touches the nodes it connects — otherwise it would visibly detach.\n *\n * This is a direct port of d3's `Basis` curve rather than a lookalike, so a\n * diagram rendered here traces the same path Mermaid would draw. For each\n * point d3 emits one cubic whose controls are the thirds between the two\n * preceding points and whose end is the B-spline knot `(p0 + 4p1 + p2) / 6`;\n * it leads in at `(5p0 + p1) / 6` and closes with a final cubic against a\n * repeated last point, then a line to it.\n */\nfunction basisPath(points: Point[]): string {\n if (points.length < 3) return linearPath(points)\n\n const last = points[points.length - 1]!\n const parts: string[] = [moveTo(points[0]!)]\n\n /** One B-spline segment across three successive control points. */\n const segment = (p0: Point, p1: Point, p2: Point): string =>\n ` C ${(2 * p0.x + p1.x) / 3} ${(2 * p0.y + p1.y) / 3},` +\n ` ${(p0.x + 2 * p1.x) / 3} ${(p0.y + 2 * p1.y) / 3},` +\n ` ${(p0.x + 4 * p1.x + p2.x) / 6} ${(p0.y + 4 * p1.y + p2.y) / 6}`\n\n // Lead-in to the first knot, one sixth of the way along the opening leg.\n parts.push(\n ` L ${(5 * points[0]!.x + points[1]!.x) / 6}` +\n ` ${(5 * points[0]!.y + points[1]!.y) / 6}`,\n )\n\n for (let i = 2; i < points.length; i++) {\n parts.push(segment(points[i - 2]!, points[i - 1]!, points[i]!))\n }\n\n // d3 closes by feeding the last point twice, then drawing to it.\n parts.push(segment(points[points.length - 2]!, last, last))\n parts.push(` L ${last.x} ${last.y}`)\n\n return parts.join('')\n}\n\n/**\n * Straight runs joined by rounded corners. Mermaid calls this `natural`.\n *\n * NOTE: this is deliberately *not* d3's interpolating natural cubic spline.\n * ELK routes edges orthogonally, so every bend is a right angle, and at a\n * right angle the tangent of any C1-smooth interpolating spline is the\n * average of the two segment directions — i.e. diagonal. The curve must then\n * arrive at the corner travelling diagonally while approaching from directly\n * above, so it bulges sideways past the straight line. Two mirrored edges\n * leaving one decision node turned that bulge into a visible teardrop loop.\n * Scaling the tangents down shrinks the overshoot but cannot remove it; it is\n * a property of interpolating a 90° corner smoothly.\n *\n * Rounding the corners instead gives a smooth path that never overshoots and\n * stays visually distinct from `basis`, which cuts corners far more deeply.\n * The trade is that the path passes *near* each bend rather than exactly\n * through it — documented in docs/diagrams.md.\n */\nfunction naturalPath(points: Point[]): string {\n if (points.length < 3) return linearPath(points)\n\n /** Largest corner radius, in px. Kept small so edges stay readable. */\n const MAX_RADIUS = 12\n\n const parts: string[] = [moveTo(points[0]!)]\n\n for (let i = 1; i < points.length - 1; i++) {\n const prev = points[i - 1]!\n const corner = points[i]!\n const next = points[i + 1]!\n\n const inLen = Math.hypot(corner.x - prev.x, corner.y - prev.y)\n const outLen = Math.hypot(next.x - corner.x, next.y - corner.y)\n\n // A zero-length leg means duplicated routed points; there is no corner\n // to round, so pass straight through.\n if (inLen === 0 || outLen === 0) {\n parts.push(` L ${corner.x} ${corner.y}`)\n continue\n }\n\n // Never consume more than half of either adjacent leg, so neighbouring\n // corners cannot overlap and invert the path.\n const radius = Math.min(MAX_RADIUS, inLen / 2, outLen / 2)\n\n const startX = corner.x - ((corner.x - prev.x) / inLen) * radius\n const startY = corner.y - ((corner.y - prev.y) / inLen) * radius\n const endX = corner.x + ((next.x - corner.x) / outLen) * radius\n const endY = corner.y + ((next.y - corner.y) / outLen) * radius\n\n // Straight up to the fillet, then a quadratic through the corner.\n parts.push(` L ${startX} ${startY}`)\n parts.push(` Q ${corner.x} ${corner.y}, ${endX} ${endY}`)\n }\n\n const last = points[points.length - 1]!\n parts.push(` L ${last.x} ${last.y}`)\n\n return parts.join('')\n}\n\n/**\n * Right-angle staircase between consecutive points.\n *\n * `where` picks when the step happens: at the midpoint (`step`), immediately\n * (`stepBefore`), or at the end of the run (`stepAfter`) — matching d3's\n * curveStep family.\n *\n * The final segment is deliberately left straight, running along the original\n * approach direction. An SVG marker with `orient=\"auto\"` takes its angle from\n * the last path segment, so a staircase that ends on a horizontal leg points\n * the arrowhead sideways into the target node instead of at it. Keeping the\n * last leg means the arrow still aims the way the routed edge does.\n */\nfunction stepPath(points: Point[], where: 'mid' | 'before' | 'after'): string {\n const parts: string[] = [moveTo(points[0]!)]\n const lastIndex = points.length - 1\n\n for (let i = 0; i < lastIndex; i++) {\n const a = points[i]!\n const b = points[i + 1]!\n\n if (i === lastIndex - 1) {\n parts.push(` L ${b.x} ${b.y}`)\n break\n }\n\n if (where === 'before') {\n parts.push(` L ${a.x} ${b.y} L ${b.x} ${b.y}`)\n } else if (where === 'after') {\n parts.push(` L ${b.x} ${a.y} L ${b.x} ${b.y}`)\n } else {\n const midY = (a.y + b.y) / 2\n parts.push(` L ${a.x} ${midY} L ${b.x} ${midY} L ${b.x} ${b.y}`)\n }\n }\n\n return parts.join('')\n}\n\n/**\n * Build the `d` attribute for an edge's routed points under `curve`.\n *\n * A path with fewer than two points cannot be drawn; the caller already skips\n * those, but this degrades to an empty string rather than emitting `M`\n * followed by nothing.\n */\nexport function pointsToPath(\n points: Point[],\n curve: CurveStyle = 'linear',\n): string {\n if (points.length === 0) return ''\n if (points.length === 1) return moveTo(points[0]!)\n\n switch (curve) {\n case 'basis':\n return basisPath(points)\n case 'natural':\n return naturalPath(points)\n case 'step':\n return stepPath(points, 'mid')\n case 'stepBefore':\n return stepPath(points, 'before')\n case 'stepAfter':\n return stepPath(points, 'after')\n case 'linear':\n default:\n return linearPath(points)\n }\n}\n","/**\n * Shared ELK instance singleton.\n *\n * Uses elk.bundled.js (pure synchronous JS, ~1.6 MB) for all environments.\n * The singleton is created lazily on first use and cached forever.\n *\n * ELK's FakeWorker wraps both postMessage and onmessage in setTimeout(0),\n * making the normal API fully async. To bypass this:\n * 1. During construction, we capture setTimeout(0) callbacks and flush them\n * synchronously — this registers the layout algorithms immediately.\n * 2. For layout calls, we call dispatcher.saveDispatch() directly (skipping\n * the FakeWorker's postMessage setTimeout) and intercept the result via\n * rawWorker.onmessage (which the dispatcher calls synchronously).\n */\n\nimport type { ELK, ElkNode } from 'elkjs'\nimport ELKBundled from 'elkjs/lib/elk.bundled.js'\nimport type { LayoutCache } from '@zombie-mermaid/core'\n\n/** The message envelope ELK's FakeWorker passes to `dispatcher.saveDispatch()`\n * to request a layout run. Mirrors the shape elk-worker.min.js expects on\n * `data` for a `cmd: 'layout'` request — not part of elkjs's public types. */\ninterface ElkWorkerRequest {\n id: number\n cmd: 'layout'\n graph: ElkNode\n}\n\n/** The message envelope ELK's dispatcher passes back to `onmessage` once a\n * layout run completes. Mirrors elk-worker.min.js's response shape for a\n * `cmd: 'layout'` request — not part of elkjs's public types. */\ninterface ElkWorkerResponse {\n id: number\n data?: ElkNode\n error?: unknown\n}\n\ninterface RawFakeWorker {\n postMessage(msg: unknown): void\n onmessage: ((e: { data: ElkWorkerResponse }) => void) | null\n dispatcher: {\n saveDispatch(msg: { data: ElkWorkerRequest }): void\n }\n}\n\n/**\n * The shape of elkjs's bundled `ELK` instance that actually exists at\n * runtime, including the internal `worker` handle. elkjs's public `ELK`\n * type (from `elk-api.d.ts`) only declares `layout`/`knownLayout*`/\n * `terminateWorker` — it deliberately doesn't expose worker internals,\n * since those aren't a supported API. We rely on them anyway (see file\n * header), so this extends the public type with the internal piece we\n * touch instead of casting through `unknown`.\n */\ninterface ElkBundledInternal extends ELK {\n worker: { worker: RawFakeWorker }\n}\n\nlet elk: ELK | null = null\nlet rawWorker: RawFakeWorker | null = null\n\n// ============================================================================\n// Opt-in layout cache\n// ============================================================================\n\n/**\n * `LayoutCache`'s shape lives in `@zombie-mermaid/core`'s `types.ts`\n * because `RenderOptions.layoutCache` references it and `core` must not\n * type-import this package (zombie-mermaid#625). Re-exported here so this\n * module stays the single import site for everything layout-cache-related,\n * exactly as before the split.\n */\nexport type { LayoutCache }\n\n/**\n * Concrete shape of a `LayoutCache`, restated locally.\n *\n * `core`'s `LayoutCache` interface marks `map`/`maxSize` `@internal`\n * (see the comment on that interface in packages/core/src/types.ts), so\n * api-extractor strips them from `@zombie-mermaid/core`'s *published*\n * `dist/index.d.ts` — deliberately, so they stay invisible to\n * `@zombie-mermaid/core` consumers and to `zombie-mermaid`'s own public\n * types (which re-export `LayoutCache` from this package). As of #769,\n * `@zombie-mermaid/core` is a real, independently-built dependency of this\n * package rather than bundled source, so this module now resolves\n * `LayoutCache` through that same trimmed public declaration too — same as\n * any other consumer. This module is the one place, in or out of `core`,\n * that actually builds and mutates those fields (`createLayoutCache()`\n * below; the cache read/evict logic in `elkLayoutSync()`), so it restates\n * the concrete shape here — kept in sync by hand with `core`'s source of\n * truth — rather than either widening `core`'s public surface or fighting\n * api-extractor's trimming in the build config to get it back.\n */\ninterface LayoutCacheShape {\n map: Map<string, ElkNode>\n maxSize: number\n}\n\nconst DEFAULT_LAYOUT_CACHE_SIZE = 20\n\n/**\n * Create a new opt-in layout cache with a bounded size (default 20\n * entries). Once full, the least-recently-used entry is evicted to make\n * room for a new one.\n *\n * Pass the result to `elkLayoutSync()` directly, or via\n * `RenderOptions.layoutCache` (threaded through by `layoutGraphSync()`,\n * `layoutClassDiagramSync()`, and `layoutErDiagramSync()`) to memoize\n * layout across repeated renders of the same diagram + options.\n */\nexport function createLayoutCache(\n maxSize: number = DEFAULT_LAYOUT_CACHE_SIZE,\n): LayoutCache {\n if (!Number.isInteger(maxSize) || maxSize < 1) {\n throw new Error(\n `createLayoutCache: maxSize must be a positive integer, got ${maxSize}`,\n )\n }\n // Typed as the concrete shape first (see `LayoutCacheShape` above), then\n // returned as the public, trimmed `LayoutCache` — a plain widening\n // assignment, not a cast.\n const cache: LayoutCacheShape = { map: new Map(), maxSize }\n return cache\n}\n\n/**\n * Deterministically serialize a value for use as a cache key, sorting\n * object keys recursively so two structurally-equal ELK input graphs\n * always produce an identical string regardless of property-insertion\n * order. Plain `JSON.stringify()` does not guarantee that — and this\n * function is the only thing standing between a cache hit and returning\n * some *other* diagram's layout, so it can't be allowed to drift with\n * insertion order.\n *\n * The ELK input graph built by `mermaidToElk()` is plain JSON (arrays,\n * plain objects, strings, numbers, booleans — the shape ELK's own JSON\n * schema requires) with every render option that affects layout already\n * baked in (direction, spacing, per-subgraph overrides, etc.), so\n * serializing the graph itself is a complete and correct cache key: equal\n * serialized input always means equal ELK output, since ELK layout is a\n * pure function of its input graph.\n */\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(',')}]`\n }\n if (value !== null && typeof value === 'object') {\n const record = value as Record<string, unknown>\n const keys = Object.keys(record).sort()\n const entries = keys.map(\n (key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`,\n )\n return `{${entries.join(',')}}`\n }\n return JSON.stringify(value)\n}\n\n/**\n * Ensure the ELK singleton exists.\n *\n * Patches setTimeout during construction to capture and synchronously flush\n * the algorithm registration callback that ELK queues via setTimeout(0).\n * Without this, layout calls fail with \"algorithm not found\" until the\n * next macrotask.\n */\nfunction ensureElk(): RawFakeWorker {\n if (elk && rawWorker) return rawWorker\n\n // Capture setTimeout(0) callbacks queued during ELK construction\n const pending: (() => void)[] = []\n const origSetTimeout = globalThis.setTimeout\n // @ts-expect-error — simplified signature for our interception, not\n // assignment-compatible with the full `typeof setTimeout` overload set\n globalThis.setTimeout = (fn: () => void, delay?: number) => {\n if (delay === 0) {\n pending.push(fn)\n return 0\n }\n return origSetTimeout(fn, delay)\n }\n\n // Bun defines `self` (= globalThis) but not `document`, which tricks\n // elk-worker.min.js into taking the Web Worker branch instead of the\n // CJS branch. Temporarily hide `self` so it exports {Worker: FakeWorker}.\n // `lib` in tsconfig.json is `[\"ESNext\"]` (no `dom`), so `self`/`document`\n // aren't ambiently declared on `globalThis` — narrow to just the two\n // properties this probe touches instead of a blanket `Record`.\n const g = globalThis as { self?: unknown; document?: unknown }\n const hadSelf = 'self' in g\n const origSelf = g.self\n if (hadSelf && typeof g.document === 'undefined') {\n delete g.self\n }\n\n elk = new ELKBundled()\n if (!elk) {\n // Unreachable — `new ELKBundled()` always returns an instance — but\n // makes the invariant explicit rather than letting the cast below\n // silently paper over a null `elk` if that ever stopped being true.\n throw new Error('ELKBundled construction unexpectedly produced no instance')\n }\n\n // Restore self\n if (hadSelf) g.self = origSelf\n\n // Restore setTimeout immediately\n globalThis.setTimeout = origSetTimeout\n\n // Flush captured callbacks synchronously — registers layout algorithms\n pending.forEach((fn) => fn())\n\n // Cache the raw FakeWorker for elkLayoutSync()\n rawWorker = (elk as ElkBundledInternal).worker.worker\n return rawWorker\n}\n\n/**\n * Run ELK layout synchronously.\n *\n * Bypasses BOTH of ELK's setTimeout(0) wrappers:\n * - FakeWorker.postMessage wraps dispatch in setTimeout(0) — bypassed by\n * calling dispatcher.saveDispatch() directly\n * - PromisedWorker.onmessage wraps receive in setTimeout(0) — bypassed by\n * replacing rawWorker.onmessage with a direct interceptor\n *\n * @param cache - Optional opt-in layout cache (see `createLayoutCache()`).\n * When provided, a cache hit returns the previous result without running\n * ELK layout again. Omitted/undefined preserves the original\n * always-recompute behavior exactly.\n */\nexport function elkLayoutSync(graph: ElkNode, cache?: LayoutCache): ElkNode {\n // `cache`, when provided, is always an object this module itself built\n // via `createLayoutCache()` above — never anything constructed outside\n // this package. Restated as the concrete shape here (see\n // `LayoutCacheShape`'s doc comment) since the public `LayoutCache` type\n // this parameter is declared with intentionally hides `map`/`maxSize`.\n const internalCache = cache as LayoutCacheShape | undefined\n const cacheKey = internalCache ? stableStringify(graph) : undefined\n if (internalCache && cacheKey !== undefined) {\n const cached = internalCache.map.get(cacheKey)\n if (cached) {\n // Mark as most-recently-used: Map iteration order follows insertion\n // order, so a delete+re-set moves this entry to the end — which is\n // what the LRU eviction below relies on to find the *least*\n // recently used entry (the current first key).\n internalCache.map.delete(cacheKey)\n internalCache.map.set(cacheKey, cached)\n return cached\n }\n }\n\n const worker = ensureElk()\n\n let result: ElkNode | undefined\n let error: unknown\n\n // Replace onmessage to intercept the result synchronously\n // (the dispatcher calls this directly, without setTimeout)\n const origOnmessage = worker.onmessage\n worker.onmessage = (answer: { data: ElkWorkerResponse }) => {\n if (answer.data.error) {\n error = answer.data.error\n } else {\n result = answer.data.data\n }\n }\n\n // Call dispatcher.saveDispatch directly — bypasses FakeWorker.postMessage's\n // setTimeout(0) wrapper. The dispatcher processes the layout synchronously\n // and calls rawWorker.onmessage with the result.\n worker.dispatcher.saveDispatch({\n data: { id: 0, cmd: 'layout', graph },\n })\n\n // Restore original handler\n worker.onmessage = origOnmessage\n\n if (error) throw error\n if (!result) throw new Error('ELK layout did not return synchronously')\n\n if (internalCache && cacheKey !== undefined) {\n internalCache.map.set(cacheKey, result)\n if (internalCache.map.size > internalCache.maxSize) {\n // Map iteration order is insertion order, so the first key is the\n // least recently used (see the recency bump on hit, above).\n const oldestKey = internalCache.map.keys().next().value\n if (oldestKey !== undefined) internalCache.map.delete(oldestKey)\n }\n }\n\n return result\n}\n","// ============================================================================\n// Font metrics — character width estimates for Inter at different sizes.\n// Used to approximate text bounding boxes without DOM measurement.\n// These are calibrated for Inter's typical glyph widths.\n//\n// NOTE: Theme/color system has moved to packages/core/src/theme.ts. This file only\n// contains font metrics, spacing constants, and stroke widths.\n// ============================================================================\n\nimport { measureTextWidth } from '@zombie-mermaid/core'\n\n/** Average character width in px at the given font size and weight (proportional font) */\nexport function estimateTextWidth(\n text: string,\n fontSize: number,\n fontWeight: number,\n): number {\n // Delegate to variable-width character measurement for better accuracy\n // with mixed character sets (Latin narrow/wide, CJK, emoji, etc.)\n return measureTextWidth(text, fontSize, fontWeight)\n}\n\n/** Average character width in px for monospace fonts (uniform glyph width) */\nexport function estimateMonoTextWidth(text: string, fontSize: number): number {\n // Monospace fonts have uniform character width — 0.6 of fontSize matches actual\n // glyph widths for JetBrains Mono / SF Mono / Fira Code at small sizes (11px).\n // Previous value of 0.55 underestimated widths, causing class member labels to\n // extend beyond their box boundaries.\n return text.length * fontSize * 0.6\n}\n\n/** Monospace font family used for code-like text (class members, types) */\nexport const MONO_FONT = \"'JetBrains Mono'\" as const\n\n/** Full CSS fallback chain for monospace text */\nexport const MONO_FONT_STACK =\n `${MONO_FONT}, 'SF Mono', 'Fira Code', ui-monospace, monospace` as const\n\n/** Default font sizes used in the renderer (in px). Overridable via `RenderOptions.fontSizes`. */\nexport const FONT_SIZES = {\n /** Node label text */\n nodeLabel: 13,\n /** Edge label text */\n edgeLabel: 11,\n /** Subgraph header text */\n groupHeader: 12,\n} as const\n\n/** Resolved font-size set — same shape as {@link FONT_SIZES} but mutable numbers. */\nexport type FontSizes = { [K in keyof typeof FONT_SIZES]: number }\n\n/** Partial font-size overrides, as accepted by `RenderOptions.fontSizes`. */\nexport type FontSizeOptions = Partial<FontSizes>\n\n/**\n * Merge user-provided font-size overrides over the {@link FONT_SIZES} defaults.\n * Any field left unspecified (or `undefined`) falls back to its default.\n */\nexport function resolveFontSizes(overrides?: FontSizeOptions): FontSizes {\n return {\n nodeLabel: overrides?.nodeLabel ?? FONT_SIZES.nodeLabel,\n edgeLabel: overrides?.edgeLabel ?? FONT_SIZES.edgeLabel,\n groupHeader: overrides?.groupHeader ?? FONT_SIZES.groupHeader,\n }\n}\n\n/** Font weights used per element type */\nexport const FONT_WEIGHTS = {\n nodeLabel: 500,\n edgeLabel: 400,\n groupHeader: 600,\n} as const\n\n// ============================================================================\n// Spacing & sizing constants\n// ============================================================================\n\n/** Vertical gap between a subgraph header band and the content area below it (px).\n * Without this, nested subgraph headers sit flush against their parent's header band.\n * Increased from 8 to 12 to provide more clearance for edges routing near headers. */\nexport const GROUP_HEADER_CONTENT_PAD = 12\n\n/** Padding inside node shapes */\nexport const NODE_PADDING = {\n /** Horizontal padding inside rectangles/rounded/stadium (increased from 16 for better label fit) */\n horizontal: 20,\n /** Vertical padding inside rectangles/rounded/stadium */\n vertical: 10,\n /** Extra padding for diamond shapes (they need more space due to rotation) */\n diamondExtra: 24,\n} as const\n\n/** Stroke widths per element type (in px) */\nexport const STROKE_WIDTHS = {\n outerBox: 1,\n innerBox: 0.75,\n /** Edge connector stroke (increased from 0.75 for better visibility) */\n connector: 1,\n} as const\n\n/**\n * Vertical shift applied to all text elements for font-agnostic centering.\n *\n * Instead of relying on `dominant-baseline=\"central\"` (which each font interprets\n * differently based on its own ascent/descent metrics), we use the default alphabetic\n * baseline and shift down by 0.35em. This places the optical center of text at the\n * y coordinate, regardless of font family (Inter, JetBrains Mono, system fallbacks).\n *\n * The 0.35em value approximates the distance from alphabetic baseline to visual\n * center of Latin text. Using `em` units ensures it scales with font size.\n */\nexport const TEXT_BASELINE_SHIFT = '0.35em' as const\n\n/** Arrow head dimensions — matches spec: 8px wide × ~5px tall */\nexport const ARROW_HEAD = {\n width: 8,\n height: 5,\n} as const\n","/**\n * Shared defaults for the ELK-based layout engine.\n * Split out of layout-engine.ts so both the ELK conversion and the\n * post-processing stages (layer alignment, elk-result conversion) can\n * reference the same values without importing the top-level module.\n */\n\nimport { FONT_SIZES } from '../styles.ts'\n\n/** Default render options (layout-only) */\nexport const DEFAULTS = {\n font: 'Inter',\n padding: 40,\n nodeSpacing: 28,\n layerSpacing: 48,\n mergeEdges: true,\n thoroughness: 3,\n fontSizes: FONT_SIZES,\n} as const\n","/**\n * Shared ELK graph-*construction* helpers.\n *\n * The reverse direction (reading geometry back out of an ELK result) lives\n * in `./elk-adapter-utils.ts`; this module is its input-side counterpart.\n *\n * Three renderers build ELK's typed JSON input independently —\n * `./to-elk.ts` (flowchart + state), `../class/layout.ts`, and\n * `../er/layout.ts`. They differ in domain model and in which extra\n * `elk.*` options they set, but the primitives underneath are identical:\n * one direction mapping, one `elk.padding` string format, one leaf-node\n * shape, and one measured edge-label box. Those primitives live here so\n * the three call sites can't drift (issue #616).\n *\n * This module deliberately does **not** try to abstract over the three\n * diagram types' own graph shapes (subgraph nesting, hierarchical ports,\n * note links) — that would be an engine-neutral intermediate graph, which\n * is the separately-scoped work in #538.\n */\n\nimport type { ElkExtendedEdge, ElkLabel, ElkNode, LayoutOptions } from 'elkjs'\nimport type { Direction } from '@zombie-mermaid/core'\nimport { measureMultilineText } from '@zombie-mermaid/core'\nimport { FONT_WEIGHTS } from '../styles.ts'\n\n// ============================================================================\n// Direction\n// ============================================================================\n\n/** The four values ELK's `elk.direction` option accepts in this codebase. */\nexport type ElkDirection = 'DOWN' | 'UP' | 'LEFT' | 'RIGHT'\n\n/**\n * The `elk.direction` each diagram type falls back to when its source\n * carries no `direction` statement.\n *\n * These genuinely differ per diagram type, and the difference is\n * intentional rather than an accident of three separate implementations:\n *\n * - **flowchart / state** — `DOWN`. `MermaidGraph.direction` is a required\n * field the parser always fills in (defaulting to `TD`), so the fallback\n * is only a type-level backstop; the effective default is mermaid's own\n * top-down flowchart default.\n * - **class** — `DOWN`. `ClassDiagram` has no direction concept at all (see\n * `ClassRenderOptions` in `packages/core/src/types.ts`: class diagrams\n * \"have no `direction` or `curve` concept\"), so this is the renderer's\n * single fixed orientation, matching Mermaid's own TB class rendering —\n * which `../class/layout.ts` relies on to put a `note for X` above its\n * class.\n * - **ER** — `RIGHT`. An ER diagram's `direction` is optional\n * (`ErDiagram.direction?`), left `undefined` when the source has no\n * `direction` statement, and this renderer has always laid those out\n * left-to-right. `direction TB`/`LR`/`BT`/`RL` in the source (or\n * `RenderOptions.direction`, applied via `withDirectionOverride`) still\n * wins over it.\n *\n * The *mappings* were never in conflict — both hand-rolled\n * `directionToElk()` implementations agreed on all five `Direction` values\n * (`LR`→`RIGHT`, `RL`→`LEFT`, `BT`→`UP`, `TD`/`TB`→`DOWN`). Only the\n * no-direction fallback differed, which is why it is a parameter here\n * rather than something to reconcile away.\n */\nexport const ELK_DIRECTION_FALLBACK = {\n flowchart: 'DOWN',\n state: 'DOWN',\n class: 'DOWN',\n er: 'RIGHT',\n} as const satisfies Record<string, ElkDirection>\n\n/**\n * Convert a Mermaid `direction` to ELK's `elk.direction` value.\n *\n * `fallback` covers the no-`direction`-statement case only — every member\n * of `Direction` has an explicit mapping. Pass the diagram type's entry\n * from `ELK_DIRECTION_FALLBACK` rather than a bare string literal, so the\n * per-type defaults stay documented in one place.\n */\nexport function directionToElk(\n dir: Direction | undefined,\n fallback: ElkDirection,\n): ElkDirection {\n switch (dir) {\n case 'LR':\n return 'RIGHT'\n case 'RL':\n return 'LEFT'\n case 'BT':\n return 'UP'\n case 'TD':\n case 'TB':\n return 'DOWN'\n default:\n return fallback\n }\n}\n\n// ============================================================================\n// Layout options\n// ============================================================================\n\n/** Per-side padding, for the asymmetric case (a subgraph's header gap). */\nexport interface ElkPaddingSides {\n top: number\n left: number\n bottom: number\n right: number\n}\n\n/**\n * Format ELK's `elk.padding` value. A single number applies to all four\n * sides; an object sets them individually.\n */\nexport function elkPadding(padding: number | ElkPaddingSides): string {\n const sides: ElkPaddingSides =\n typeof padding === 'number'\n ? { top: padding, left: padding, bottom: padding, right: padding }\n : padding\n return `[top=${sides.top},left=${sides.left},bottom=${sides.bottom},right=${sides.right}]`\n}\n\n/**\n * The `elk.*` options every one of the three graph builders sets, with the\n * values they all agree on. Callers spread the result and add their own\n * diagram-specific options on top.\n */\nexport function baseElkLayoutOptions(spec: {\n direction: ElkDirection\n nodeSpacing: number\n layerSpacing: number\n padding: number | ElkPaddingSides\n}): LayoutOptions {\n return {\n 'elk.algorithm': 'layered',\n 'elk.direction': spec.direction,\n 'elk.spacing.nodeNode': String(spec.nodeSpacing),\n 'elk.layered.spacing.nodeNodeBetweenLayers': String(spec.layerSpacing),\n 'elk.padding': elkPadding(spec.padding),\n 'elk.edgeRouting': 'ORTHOGONAL',\n }\n}\n\n// ============================================================================\n// Nodes\n// ============================================================================\n\n/**\n * Build a leaf ELK node.\n *\n * `label` is attached as an ELK label when supplied — the flowchart/state\n * path does that (ELK reads it for nothing, but `from-elk.ts` reads it\n * back); class and ER carry labels in their own side tables instead and\n * omit it. An empty-string label is still a label, so the check is\n * `undefined`, not truthiness.\n */\nexport function buildElkLeafNode(\n id: string,\n size: { width: number; height: number },\n label?: string,\n): ElkNode {\n const node: ElkNode = { id, width: size.width, height: size.height }\n if (label !== undefined) node.labels = [{ text: label }]\n return node\n}\n\n// ============================================================================\n// Edges\n// ============================================================================\n\n/**\n * Per-label layout options the flowchart/state path sets on every edge\n * label. Class and ER instead set `elk.edgeLabels.placement` once on the\n * root graph, so they pass no per-label options.\n */\nexport const INLINE_CENTERED_EDGE_LABEL: LayoutOptions = {\n 'elk.edgeLabels.inline': 'true',\n 'elk.edgeLabels.placement': 'CENTER',\n}\n\n/**\n * How a caller wants its edge labels measured and placed. `fontSize` is\n * the resolved `fontSizes.edgeLabel`; `layoutOptions`, when given, is\n * attached to the ELK label itself.\n */\nexport interface ElkEdgeLabelStyle {\n fontSize: number\n layoutOptions?: LayoutOptions\n}\n\n/**\n * Measure an edge label and build ELK's label box for it.\n *\n * The `+8` / `+6` are the horizontal/vertical breathing room all three\n * builders have always added around the measured text so ELK reserves a\n * slightly larger channel than the glyphs strictly need.\n */\nexport function buildElkEdgeLabel(\n text: string,\n style: ElkEdgeLabelStyle,\n): ElkLabel {\n const metrics = measureMultilineText(\n text,\n style.fontSize,\n FONT_WEIGHTS.edgeLabel,\n )\n const label: ElkLabel = {\n text,\n width: metrics.width + 8,\n height: metrics.height + 6,\n }\n // Copied, not aliased: every label previously got its own fresh options\n // literal, and ELK receives this graph by reference (see\n // `elkLayoutSync`'s `saveDispatch`) rather than through a structured\n // clone — so a shared object would be shared into ELK's own hands too.\n if (style.layoutOptions) label.layoutOptions = { ...style.layoutOptions }\n return label\n}\n\n/**\n * Build a single-source/single-target ELK edge, with a measured label when\n * `label` is non-empty. An absent or empty label leaves `labels` off the\n * edge entirely (rather than setting it to an empty array or `undefined`),\n * matching what all three builders did by hand.\n */\nexport function buildElkEdge(spec: {\n id: string\n source: string\n target: string\n label?: string\n labelStyle: ElkEdgeLabelStyle\n}): ElkExtendedEdge {\n const edge: ElkExtendedEdge = {\n id: spec.id,\n sources: [spec.source],\n targets: [spec.target],\n }\n if (spec.label) {\n edge.labels = [buildElkEdgeLabel(spec.label, spec.labelStyle)]\n }\n return edge\n}\n","/**\n * Graph conversion: MermaidGraph → ELK JSON.\n *\n * Split out of layout-engine.ts. This module only handles building ELK's\n * nested JSON input format from a parsed MermaidGraph — the reverse\n * direction (ELK result → PositionedGraph) lives in ./from-elk.ts.\n */\n\nimport type { ElkNode, ElkExtendedEdge, LayoutOptions } from 'elkjs'\nimport type {\n MermaidGraph,\n MermaidSubgraph,\n MermaidEdge,\n RenderOptions,\n} from '@zombie-mermaid/core'\nimport type { FontSizes } from '../styles.ts'\nimport { FONT_WEIGHTS, NODE_PADDING } from '../styles.ts'\nimport { measureMultilineText } from '@zombie-mermaid/core'\nimport { DEFAULTS } from './constants.ts'\nimport {\n ELK_DIRECTION_FALLBACK,\n INLINE_CENTERED_EDGE_LABEL,\n baseElkLayoutOptions,\n buildElkEdge,\n buildElkLeafNode,\n directionToElk as sharedDirectionToElk,\n type ElkDirection,\n type ElkEdgeLabelStyle,\n type ElkPaddingSides,\n} from './elk-graph-builder.ts'\n\n/**\n * Convert a Mermaid direction to an ELK direction, flowchart/state-style\n * (no direction at all → `DOWN`; see `ELK_DIRECTION_FALLBACK`).\n *\n * `MermaidGraph.direction` is required, so the fallback is unreachable\n * from this path in practice — it exists so every `Direction` maps through\n * the one shared table.\n */\nfunction directionToElk(dir: MermaidGraph['direction']): ElkDirection {\n return sharedDirectionToElk(dir, ELK_DIRECTION_FALLBACK.flowchart)\n}\n\n/**\n * The `elk.*` options the root graph and every subgraph container share.\n * Both add `elk.direction`, `elk.padding` and their own extras on top.\n */\nfunction containerLayoutOptions(spec: {\n direction: ElkDirection\n nodeSpacing: number\n layerSpacing: number\n padding: number | ElkPaddingSides\n}): LayoutOptions {\n return {\n ...baseElkLayoutOptions(spec),\n 'elk.spacing.edgeEdge': '12',\n 'elk.layered.spacing.edgeEdgeBetweenLayers': '12',\n 'elk.layered.spacing.edgeNodeBetweenLayers': '12',\n 'elk.layered.nodePlacement.bk.fixedAlignment': 'BALANCED',\n 'elk.contentAlignment': 'H_CENTER V_CENTER',\n }\n}\n\n/**\n * How flowchart/state edge labels are measured and placed: at the resolved\n * edge-label font size, inline and centered on the edge.\n */\nfunction edgeLabelStyle(opts: { fontSizes: FontSizes }): ElkEdgeLabelStyle {\n return {\n fontSize: opts.fontSizes.edgeLabel,\n layoutOptions: INLINE_CENTERED_EDGE_LABEL,\n }\n}\n\n// ============================================================================\n// Node sizing\n// ============================================================================\n\nfunction estimateNodeSize(\n id: string,\n label: string,\n shape: string,\n nodeLabelFontSize: number,\n): { width: number; height: number } {\n const metrics = measureMultilineText(\n label,\n nodeLabelFontSize,\n FONT_WEIGHTS.nodeLabel,\n )\n\n let width = metrics.width + NODE_PADDING.horizontal * 2\n let height = metrics.height + NODE_PADDING.vertical * 2\n\n if (shape === 'diamond') {\n const side = Math.max(width, height) + NODE_PADDING.diamondExtra\n width = side\n height = side\n }\n\n if (shape === 'circle' || shape === 'doublecircle') {\n const diameter = Math.ceil(Math.sqrt(width * width + height * height)) + 8\n width = shape === 'doublecircle' ? diameter + 12 : diameter\n height = width\n }\n\n if (shape === 'hexagon') {\n width += NODE_PADDING.horizontal\n }\n\n if (\n shape === 'trapezoid' ||\n shape === 'trapezoid-alt' ||\n shape === 'parallelogram' ||\n shape === 'parallelogram-alt'\n ) {\n // Sloped sides eat horizontal room at the label's baseline, so widen to\n // keep the label inside the polygon.\n width += NODE_PADDING.horizontal\n }\n\n if (shape === 'asymmetric') {\n width += 12\n }\n\n if (shape === 'cylinder') {\n height += 14\n }\n\n if (shape === 'state-start' || shape === 'state-end') {\n return { width: 28, height: 28 }\n }\n\n width = Math.max(width, 60)\n height = Math.max(height, 36)\n\n return { width, height }\n}\n\n// ============================================================================\n// Graph conversion: MermaidGraph → ELK JSON\n// ============================================================================\n\nexport interface ElkGraphNode extends ElkNode {\n children?: ElkGraphNode[]\n edges?: ElkExtendedEdge[]\n}\n\n/** Sentinel container key for hop/bridge edges that belong at the root level. */\nconst ROOT_CONTAINER = ' root'\n\n/**\n * Recursively check whether any subgraph (at any nesting depth) has a\n * direction override. The previous implementation only scanned top-level\n * subgraphs, silently missing overrides on nested subgraphs — which meant\n * `hierarchyHandling` stayed at `INCLUDE_CHILDREN` (where ELK ignores a\n * compound node's own `elk.direction`) for exactly the diagrams that need\n * `SEPARATE` the most.\n */\nfunction hasAnyDirectionOverride(subgraphs: MermaidSubgraph[]): boolean {\n for (const sg of subgraphs) {\n if (sg.direction !== undefined) return true\n if (hasAnyDirectionOverride(sg.children)) return true\n }\n return false\n}\n\n/** Recursively collect every node ID that is a member of `sg`, including nested descendants. */\nfunction collectAllMemberNodeIds(sg: MermaidSubgraph, out: Set<string>): void {\n for (const id of sg.nodeIds) out.add(id)\n for (const child of sg.children) collectAllMemberNodeIds(child, out)\n}\n\n/**\n * Real mermaid.js ignores a subgraph's own `direction` override once any of\n * its member nodes (including nested descendants) has an edge to something\n * outside the subgraph — the subgraph then inherits its parent's direction\n * instead. Per the docs: \"If any of a subgraph's nodes are linked to the\n * outside, subgraph direction will be ignored. Instead the subgraph will\n * inherit the direction of the parent graph.\"\n * https://mermaid.js.org/syntax/flowchart.html\n *\n * Verified against real mermaid.js output for nested subgraphs with\n * `direction LR` and an edge crossing the boundary (e.g. `B --> X` where X\n * is inside the subgraph): the crossing-linked nodes render at the same\n * x-coordinate with increasing y — stacked per the parent's direction,\n * despite the subgraph's own `direction LR`. Mirrors the equivalent ASCII\n * fix in src/ascii/converter.ts (subgraphDirectionIsHonored, issue #445).\n */\nfunction subgraphDirectionIsHonored(\n sg: MermaidSubgraph,\n graph: MermaidGraph,\n): boolean {\n const memberIds = new Set<string>()\n collectAllMemberNodeIds(sg, memberIds)\n if (memberIds.size === 0) return true\n\n const isInside = (id: string): boolean => memberIds.has(id) || id === sg.id\n\n return !graph.edges.some((e) => isInside(e.source) !== isInside(e.target))\n}\n\n/**\n * Build a map from subgraph ID to its full ancestor chain, outermost first,\n * ending with the subgraph itself (e.g. Inner nested in Outer → [\"Outer\", \"Inner\"]).\n */\nfunction buildSubgraphChains(\n subgraphs: MermaidSubgraph[],\n): Map<string, string[]> {\n const chains = new Map<string, string[]>()\n function walk(sg: MermaidSubgraph, ancestors: string[]): void {\n const chain = [...ancestors, sg.id]\n chains.set(sg.id, chain)\n for (const child of sg.children) walk(child, chain)\n }\n for (const sg of subgraphs) walk(sg, [])\n return chains\n}\n\n/** Length of the shared prefix of two ancestor chains. */\nfunction commonPrefixLength(a: string[], b: string[]): number {\n let i = 0\n while (i < a.length && i < b.length && a[i] === b[i]) i++\n return i\n}\n\n/**\n * Convert a MermaidGraph to ELK's nested JSON input format.\n *\n * Uses SEPARATE hierarchy handling for proper subgraph direction override support.\n * Cross-hierarchy edges use hierarchical ports to connect external and internal sections.\n */\nexport function mermaidToElk(\n graph: MermaidGraph,\n opts: Required<\n Pick<RenderOptions, 'font' | 'padding' | 'nodeSpacing' | 'layerSpacing'>\n > & { fontSizes: FontSizes },\n): ElkGraphNode {\n // Collect all node IDs that belong to subgraphs\n const subgraphNodeIds = new Set<string>()\n const subgraphIds = new Set<string>()\n for (const sg of graph.subgraphs) {\n subgraphIds.add(sg.id)\n collectSubgraphNodeIds(sg, subgraphNodeIds, subgraphIds)\n }\n\n // Build node-to-subgraph mapping for edge distribution\n const nodeToSubgraph = buildNodeToSubgraphMap(graph.subgraphs)\n\n // Full ancestor chain (outermost → innermost) for every subgraph, used to\n // decompose edges that cross more than one subgraph boundary.\n const subgraphChains = buildSubgraphChains(graph.subgraphs)\n const chainOf = (sgId: string | undefined): string[] =>\n sgId ? (subgraphChains.get(sgId) ?? []) : []\n\n // Classify edges into three categories:\n // 1. Internal edges (both endpoints in the same innermost subgraph)\n // 2. Root-level edges (neither endpoint in a subgraph)\n // 3. Cross-hierarchy edges (endpoints in different levels, possibly\n // several subgraph boundaries apart)\n const edgesBySubgraph = new Map<\n string | null,\n Array<{ index: number; edge: (typeof graph.edges)[0] }>\n >()\n edgesBySubgraph.set(null, []) // Root-level edges\n\n // Cross-hierarchy edges carry the full ancestor chain for each endpoint so\n // we can decompose them into a chain of sub-edges (one per crossed\n // boundary) rather than assuming a single hop.\n const crossHierarchyEdges: Array<{\n index: number\n edge: (typeof graph.edges)[0]\n srcChain: string[]\n tgtChain: string[]\n }> = []\n\n for (let i = 0; i < graph.edges.length; i++) {\n const edge = graph.edges[i]!\n const sourceSubgraph = nodeToSubgraph.get(edge.source)\n const targetSubgraph = nodeToSubgraph.get(edge.target)\n\n if (sourceSubgraph && sourceSubgraph === targetSubgraph) {\n // Internal edge: both endpoints in same innermost subgraph\n const internal = edgesBySubgraph.get(sourceSubgraph) ?? []\n edgesBySubgraph.set(sourceSubgraph, internal)\n internal.push({ index: i, edge })\n } else if (!sourceSubgraph && !targetSubgraph) {\n // Root-level edge: neither endpoint in a subgraph\n edgesBySubgraph.get(null)!.push({ index: i, edge })\n } else {\n // Cross-hierarchy edge: may cross one or more subgraph boundaries\n crossHierarchyEdges.push({\n index: i,\n edge,\n srcChain: chainOf(sourceSubgraph),\n tgtChain: chainOf(targetSubgraph),\n })\n }\n }\n\n // Determine if we need SEPARATE hierarchy handling.\n // We use SEPARATE when any subgraph — at any nesting depth — has a\n // direction override, since ELK's INCLUDE_CHILDREN mode ignores a nested\n // compound node's own `elk.direction`.\n const hasDirectionOverride = hasAnyDirectionOverride(graph.subgraphs)\n\n // Root ELK graph's children/edges — built up below across several loops,\n // then assembled into the elkGraph object literal at the very end so its\n // fields never need to be re-read through the (optional-in-the-library)\n // ElkNode.children/edges types.\n const rootChildren: ElkGraphNode[] = []\n const rootEdges: ElkExtendedEdge[] = []\n\n // Build the root ELK graph's layout options up front — the graph object\n // itself is assembled at the end, once rootChildren/rootEdges are full.\n const rootLayoutOptions: LayoutOptions = {\n ...containerLayoutOptions({\n direction: directionToElk(graph.direction),\n nodeSpacing: opts.nodeSpacing,\n layerSpacing: opts.layerSpacing,\n padding: opts.padding,\n }),\n 'elk.layered.thoroughness': String(DEFAULTS.thoroughness),\n 'elk.layered.highDegreeNodes.treatment': 'true',\n 'elk.layered.highDegreeNodes.threshold': '8',\n 'elk.layered.compaction.postCompaction.strategy':\n 'LEFT_RIGHT_CONSTRAINT_LOCKING',\n 'elk.layered.considerModelOrder.strategy': 'NODES_AND_EDGES',\n 'elk.layered.wrapping.strategy': 'OFF',\n // Use SEPARATE when subgraphs have direction overrides (enables proper direction handling)\n // Use INCLUDE_CHILDREN otherwise (simpler cross-hierarchy edge routing)\n 'elk.hierarchyHandling': hasDirectionOverride\n ? 'SEPARATE'\n : 'INCLUDE_CHILDREN',\n }\n\n // Ports to declare on each subgraph's ELK node, keyed by subgraph ID.\n const portsBySubgraph = new Map<string, Set<string>>()\n // Hop/bridge edges to inject into each container's own `edges` array.\n // Keyed by subgraph ID, or ROOT_CONTAINER for the top-level graph.\n const hopEdgesByContainer = new Map<string, ElkExtendedEdge[]>()\n\n function addPort(sgId: string, portId: string): void {\n const ports = portsBySubgraph.get(sgId) ?? new Set<string>()\n portsBySubgraph.set(sgId, ports)\n ports.add(portId)\n }\n function addHopEdge(containerId: string, elkEdge: ElkExtendedEdge): void {\n const edges = hopEdgesByContainer.get(containerId) ?? []\n hopEdgesByContainer.set(containerId, edges)\n edges.push(elkEdge)\n }\n\n // Decompose each cross-hierarchy edge into a chain of sub-edges, one per\n // subgraph boundary crossed, joined at explicit ELK ports. This is\n // required under `hierarchyHandling: SEPARATE`: ELK only resolves an edge\n // automatically when both endpoints are visible from the edge's container\n // (a direct child, or a port owned by the container or a direct child of\n // it) — it does not search further down the hierarchy. A single-hop port\n // (as used previously) is therefore only correct when one endpoint is a\n // direct child of the other endpoint's subgraph; for deeper nesting ELK\n // silently fails to route the edge at all (no sections in the result).\n //\n // For each side that needs to cross N boundaries to reach the lowest\n // common ancestor (LCA) of the source and target subgraphs, we add one\n // port per boundary and chain sub-edges between them, walking outward\n // from the endpoint to the LCA. The final \"bridge\" sub-edge (still ID\n // `e{index}`, carrying the edge label) lives directly in the LCA's own\n // `edges` array (or at root, if the LCA is the root graph).\n if (hasDirectionOverride) {\n for (const { index, edge, srcChain, tgtChain } of crossHierarchyEdges) {\n const commonLen = commonPrefixLength(srcChain, tgtChain)\n\n let sourceRef = edge.source\n if (srcChain.length > commonLen) {\n for (let i = srcChain.length - 1; i >= commonLen; i--) {\n const sgId = srcChain[i]!\n const portId = `${sgId}_out_${index}`\n addPort(sgId, portId)\n const fromRef =\n i === srcChain.length - 1\n ? edge.source\n : `${srcChain[i + 1]}_out_${index}`\n addHopEdge(sgId, {\n id: `e${index}_s${i}`,\n sources: [fromRef],\n targets: [portId],\n })\n }\n sourceRef = `${srcChain[commonLen]}_out_${index}`\n }\n\n let targetRef = edge.target\n if (tgtChain.length > commonLen) {\n for (let i = commonLen; i <= tgtChain.length - 1; i++) {\n const sgId = tgtChain[i]!\n const portId = `${sgId}_in_${index}`\n addPort(sgId, portId)\n const toRef =\n i === tgtChain.length - 1\n ? edge.target\n : `${tgtChain[i + 1]}_in_${index}`\n addHopEdge(sgId, {\n id: `e${index}_t${i}`,\n sources: [portId],\n targets: [toRef],\n })\n }\n targetRef = `${tgtChain[commonLen]}_in_${index}`\n }\n\n const bridgeContainer =\n commonLen > 0 ? srcChain[commonLen - 1]! : ROOT_CONTAINER\n addHopEdge(\n bridgeContainer,\n buildElkEdge({\n id: `e${index}`,\n source: sourceRef,\n target: targetRef,\n label: edge.label,\n labelStyle: edgeLabelStyle(opts),\n }),\n )\n }\n }\n\n // Add top-level nodes (those not in any subgraph).\n for (const [id, node] of graph.nodes) {\n if (!subgraphNodeIds.has(id) && !subgraphIds.has(id)) {\n const size = estimateNodeSize(\n id,\n node.label,\n node.shape,\n opts.fontSizes.nodeLabel,\n )\n rootChildren.push(buildElkLeafNode(id, size, node.label))\n }\n }\n\n // Add subgraphs as compound nodes with children and their internal\n // edges. Sibling subgraphs are appended in *reverse* declaration order\n // (only relative to each other — their position as a group, before or\n // after the top-level leaf nodes above, is left as-is) to match real\n // mermaid.js's own sibling-subgraph order, which is what ELK's\n // `considerModelOrder` uses as a tie-break during crossing minimization\n // and thus what ultimately decides left-right sibling order.\n //\n // Verified against mermaid@11.17.2's bundled flowDb.getData()\n // (node_modules/mermaid/dist/mermaid.min.js): it builds the node list fed\n // to the layout engine by iterating the parsed `subGraphs` array\n // *backwards* to emit cluster/subgraph nodes. Since a compound node's\n // `children()` order in the resulting graph is exactly the order its\n // child nodes were inserted, sibling subgraphs under the same parent end\n // up in reversed declaration order — see issue #444.\n //\n // Deliberately scoped to *only* the sibling-subgraph reversal, not a\n // wholesale \"all clusters before all leaves\" reordering (which mermaid's\n // own mechanism also does, globally): moving the whole subgraphs group\n // ahead of top-level leaf nodes changed which edge in a cycle ELK treats\n // as a feedback edge for a sample mixing top-level leaves and a\n // subgraph in a cyclic flow (e.g. \"CI/CD Pipeline\"), reordering the\n // entire rank structure rather than just left-right sibling position —\n // a much bigger, unreviewed blast radius than the reported bug needs.\n for (const sg of [...graph.subgraphs].reverse()) {\n rootChildren.push(\n subgraphToElk(\n sg,\n graph,\n opts,\n edgesBySubgraph,\n portsBySubgraph,\n hopEdgesByContainer,\n graph.direction,\n ),\n )\n }\n\n // Add root-level edges. Self-loops (edge.source === edge.target) are\n // excluded — ELK has no native self-loop layout and produces a degenerate\n // zero-length-span polyline for them; from-elk.ts synthesizes a proper\n // side loop for these once node positions are known instead.\n for (const { index, edge } of edgesBySubgraph.get(null)!) {\n if (edge.source === edge.target) continue\n rootEdges.push(\n buildElkEdge({\n id: `e${index}`,\n source: edge.source,\n target: edge.target,\n label: edge.label,\n labelStyle: edgeLabelStyle(opts),\n }),\n )\n }\n\n if (hasDirectionOverride) {\n // Cross-hierarchy edges were already decomposed into hop/bridge edges\n // above; the ones whose LCA is the root graph belong here.\n for (const elkEdge of hopEdgesByContainer.get(ROOT_CONTAINER) ?? []) {\n rootEdges.push(elkEdge)\n }\n } else {\n // No direction overrides anywhere: hierarchyHandling is INCLUDE_CHILDREN,\n // which lets ELK route cross-hierarchy edges automatically as long as\n // they're declared with their raw node IDs (no port decomposition\n // needed).\n for (const { index, edge } of crossHierarchyEdges) {\n rootEdges.push(\n buildElkEdge({\n id: `e${index}`,\n source: edge.source,\n target: edge.target,\n label: edge.label,\n labelStyle: edgeLabelStyle(opts),\n }),\n )\n }\n }\n\n return {\n id: 'root',\n layoutOptions: rootLayoutOptions,\n children: rootChildren,\n edges: rootEdges,\n }\n}\n\n/**\n * Convert a MermaidSubgraph to an ELK compound node.\n * Includes internal edges (edges where both endpoints are in this subgraph)\n * so that the subgraph's direction override is respected by ELK.\n *\n * When using SEPARATE hierarchy handling (for direction override support),\n * also adds hierarchical ports for cross-hierarchy edges.\n *\n * `inheritedDirection` is the effective direction of the nearest ancestor\n * (a subgraph's own honored override, or ultimately the root graph's\n * direction) — used when this subgraph's own override is dropped per\n * `subgraphDirectionIsHonored`. Explicitly propagating it, rather than\n * leaving `elk.direction` unset and relying on ELK's own property\n * inheritance under `hierarchyHandling: SEPARATE`, keeps the \"inherit the\n * parent's direction\" behavior correct and independent of ELK's inheritance\n * semantics for that property.\n */\nfunction subgraphToElk(\n sg: MermaidSubgraph,\n graph: MermaidGraph,\n opts: Required<\n Pick<RenderOptions, 'font' | 'padding' | 'nodeSpacing' | 'layerSpacing'>\n > & { fontSizes: FontSizes },\n edgesBySubgraph: Map<\n string | null,\n Array<{ index: number; edge: MermaidEdge }>\n >,\n portsBySubgraph: Map<string, Set<string>>,\n hopEdgesByContainer: Map<string, ElkExtendedEdge[]>,\n inheritedDirection: MermaidGraph['direction'],\n): ElkGraphNode {\n // Apply this subgraph's own direction override only if it's actually\n // honored (no member node has an edge crossing the boundary); otherwise\n // fall back to the inherited (parent) direction, per mermaid.js's\n // documented precedence rule.\n const effectiveDirection =\n sg.direction !== undefined && subgraphDirectionIsHonored(sg, graph)\n ? sg.direction\n : inheritedDirection\n\n const layoutOptions: LayoutOptions = containerLayoutOptions({\n direction: directionToElk(effectiveDirection),\n nodeSpacing: opts.nodeSpacing,\n layerSpacing: opts.layerSpacing,\n // Top = headerHeight(28) + gap(16) to match bottom padding\n padding: { top: 44, left: 16, bottom: 16, right: 16 },\n })\n\n // Ports, built before children/edges since they don't depend on them.\n let elkPorts: ElkNode['ports']\n const ports = portsBySubgraph.get(sg.id)\n if (ports && ports.size > 0) {\n elkPorts = [...ports].map((portId) => ({\n id: portId,\n // Port side is determined by ELK based on edge direction\n }))\n }\n\n // Add direct child (leaf) nodes, in forward declaration order.\n const children: ElkGraphNode[] = []\n for (const nodeId of sg.nodeIds) {\n const node = graph.nodes.get(nodeId)\n if (node) {\n const size = estimateNodeSize(\n nodeId,\n node.label,\n node.shape,\n opts.fontSizes.nodeLabel,\n )\n children.push(buildElkLeafNode(nodeId, size, node.label))\n }\n }\n\n // Add nested subgraphs recursively, in reverse declaration order relative\n // to *each other* only (their position as a group, before or after the\n // leaf nodes above, is left as-is — see the matching comment and\n // rationale in `mermaidToElk`) — matching real mermaid.js's own sibling-\n // subgraph order (issue #444).\n for (const child of [...sg.children].reverse()) {\n children.push(\n subgraphToElk(\n child,\n graph,\n opts,\n edgesBySubgraph,\n portsBySubgraph,\n hopEdgesByContainer,\n effectiveDirection,\n ),\n )\n }\n\n // Add internal edges (edges where both endpoints are in this subgraph).\n // Self-loops are excluded — see the matching comment in mermaidToElk.\n const edges: ElkExtendedEdge[] = []\n const internalEdges = edgesBySubgraph.get(sg.id) ?? []\n for (const { index, edge } of internalEdges) {\n if (edge.source === edge.target) continue\n edges.push(\n buildElkEdge({\n id: `e${index}`,\n source: edge.source,\n target: edge.target,\n label: edge.label,\n labelStyle: edgeLabelStyle(opts),\n }),\n )\n }\n\n // Add hop/bridge edges whose lowest common ancestor is this subgraph.\n // These connect boundary ports to internal nodes (single-hop case), chain\n // ports between two nested levels (multi-hop case), or bridge two ports\n // belonging to direct children of this subgraph (the actual crossing\n // point between the source-side and target-side chains).\n for (const elkEdge of hopEdgesByContainer.get(sg.id) ?? []) {\n edges.push(elkEdge)\n }\n\n return {\n id: sg.id,\n layoutOptions,\n labels: sg.label ? [{ text: sg.label }] : undefined,\n ports: elkPorts,\n children,\n edges,\n }\n}\n\n/** Recursively collect all node IDs that belong to any subgraph */\nfunction collectSubgraphNodeIds(\n sg: MermaidSubgraph,\n nodeIds: Set<string>,\n subgraphIds: Set<string>,\n): void {\n for (const id of sg.nodeIds) {\n nodeIds.add(id)\n }\n for (const child of sg.children) {\n subgraphIds.add(child.id)\n collectSubgraphNodeIds(child, nodeIds, subgraphIds)\n }\n}\n\n/**\n * Build a mapping from node ID to its containing subgraph ID.\n * For nested subgraphs, maps to the innermost containing subgraph.\n * Nodes not in any subgraph are not included in the map.\n */\nfunction buildNodeToSubgraphMap(\n subgraphs: MermaidSubgraph[],\n): Map<string, string> {\n const map = new Map<string, string>()\n\n function traverse(sg: MermaidSubgraph): void {\n // Map all direct child nodes to this subgraph\n for (const nodeId of sg.nodeIds) {\n map.set(nodeId, sg.id)\n }\n // Recursively process nested subgraphs (they override parent mapping)\n for (const child of sg.children) {\n traverse(child)\n }\n }\n\n for (const sg of subgraphs) {\n traverse(sg)\n }\n\n return map\n}\n","/**\n * Shape-aware edge clipping utilities.\n *\n * ELK.js treats all nodes as rectangles for edge routing. For non-rectangular\n * shapes like diamonds, this causes edges to terminate at the bounding box\n * boundary instead of the actual shape vertices.\n *\n * This module provides utilities to clip edge endpoints to actual shape\n * boundaries after ELK layout is complete.\n */\n\nimport type { Point, PositionedNode } from '@zombie-mermaid/core'\n\n/**\n * Clip an edge endpoint to the actual shape boundary of a node.\n *\n * @param points - The edge points array\n * @param node - The node to clip to\n * @param isStart - True if clipping the start point (source), false for end (target)\n * @returns New points array with clipped endpoint\n */\nexport function clipEdgeToShape(\n points: Point[],\n node: PositionedNode,\n isStart: boolean,\n): Point[] {\n if (points.length < 2) return points\n\n // Only clip non-rectangular shapes\n if (\n node.shape === 'rectangle' ||\n node.shape === 'rounded' ||\n node.shape === 'stadium'\n ) {\n return points\n }\n\n const result = [...points]\n\n if (node.shape === 'diamond') {\n if (isStart) {\n result[0] = clipToDiamond(points[0]!, points[1]!, node)\n } else {\n const lastIdx = points.length - 1\n result[lastIdx] = clipToDiamond(\n points[lastIdx]!,\n points[lastIdx - 1]!,\n node,\n )\n }\n }\n // Future: add clipping for hexagon, circle, etc.\n\n return result\n}\n\n/**\n * Clip a point to the diamond shape boundary using ray-polygon intersection.\n *\n * Diamond vertices are at the midpoints of the bounding box sides:\n * - Top: (cx, y)\n * - Right: (x + w, cy)\n * - Bottom: (cx, y + h)\n * - Left: (x, cy)\n *\n * For orthogonal edges, we extend the final segment as a ray and find where\n * it intersects the diamond boundary. This preserves orthogonality while\n * ensuring the edge terminates at the actual diamond shape.\n *\n * @param endpoint - The edge endpoint to clip\n * @param adjacent - The adjacent point (to determine ray direction)\n * @param node - The diamond node\n * @returns Clipped point on the diamond boundary\n */\nfunction clipToDiamond(\n endpoint: Point,\n adjacent: Point,\n node: PositionedNode,\n): Point {\n const cx = node.x + node.width / 2\n const cy = node.y + node.height / 2\n\n // Diamond vertices\n const top: Point = { x: cx, y: node.y }\n const right: Point = { x: node.x + node.width, y: cy }\n const bottom: Point = { x: cx, y: node.y + node.height }\n const left: Point = { x: node.x, y: cy }\n\n // Determine approach direction from adjacent point\n const dx = endpoint.x - adjacent.x\n const dy = endpoint.y - adjacent.y\n\n // For orthogonal edges, one of dx or dy will be ~0\n const isVertical = Math.abs(dx) < Math.abs(dy)\n\n if (isVertical) {\n // Vertical ray at x = endpoint.x\n const rayX = endpoint.x\n\n if (dy > 0) {\n // Coming from above (moving down) → intersect with top half of diamond\n // Top half edges: left-top (left → top) and top-right (top → right)\n if (rayX <= cx) {\n // Intersect with left-top edge (from left vertex to top vertex)\n return intersectVerticalRayWithEdge(rayX, left, top) ?? top\n } else {\n // Intersect with top-right edge (from top vertex to right vertex)\n return intersectVerticalRayWithEdge(rayX, top, right) ?? top\n }\n } else {\n // Coming from below (moving up) → intersect with bottom half of diamond\n // Bottom half edges: left-bottom (bottom → left) and bottom-right (right → bottom)\n if (rayX <= cx) {\n // Intersect with bottom-left edge (from bottom vertex to left vertex)\n return intersectVerticalRayWithEdge(rayX, bottom, left) ?? bottom\n } else {\n // Intersect with bottom-right edge (from right vertex to bottom vertex)\n return intersectVerticalRayWithEdge(rayX, right, bottom) ?? bottom\n }\n }\n } else {\n // Horizontal ray at y = endpoint.y\n const rayY = endpoint.y\n\n if (dx > 0) {\n // Coming from left (moving right) → intersect with left half of diamond\n // Left half edges: top-left (top → left) and left-bottom (left → bottom)\n if (rayY <= cy) {\n // Intersect with top-left edge (from top vertex to left vertex)\n return intersectHorizontalRayWithEdge(rayY, top, left) ?? left\n } else {\n // Intersect with left-bottom edge (from left vertex to bottom vertex)\n return intersectHorizontalRayWithEdge(rayY, left, bottom) ?? left\n }\n } else {\n // Coming from right (moving left) → intersect with right half of diamond\n // Right half edges: top-right (top → right) and right-bottom (right → bottom)\n if (rayY <= cy) {\n // Intersect with top-right edge (from top vertex to right vertex)\n return intersectHorizontalRayWithEdge(rayY, top, right) ?? right\n } else {\n // Intersect with right-bottom edge (from right vertex to bottom vertex)\n return intersectHorizontalRayWithEdge(rayY, right, bottom) ?? right\n }\n }\n }\n}\n\n/**\n * Find intersection of a horizontal ray (y = rayY) with a line segment.\n * Returns the intersection point or null if no intersection.\n */\nfunction intersectHorizontalRayWithEdge(\n rayY: number,\n p1: Point,\n p2: Point,\n): Point | null {\n const dy = p2.y - p1.y\n if (Math.abs(dy) < 0.001) {\n // Edge is horizontal, no single intersection\n return null\n }\n\n const t = (rayY - p1.y) / dy\n if (t < 0 || t > 1) {\n // Intersection outside the edge segment\n return null\n }\n\n const x = p1.x + t * (p2.x - p1.x)\n return { x, y: rayY }\n}\n\n/**\n * Find intersection of a vertical ray (x = rayX) with a line segment.\n * Returns the intersection point or null if no intersection.\n */\nfunction intersectVerticalRayWithEdge(\n rayX: number,\n p1: Point,\n p2: Point,\n): Point | null {\n const dx = p2.x - p1.x\n if (Math.abs(dx) < 0.001) {\n // Edge is vertical, no single intersection\n return null\n }\n\n const t = (rayX - p1.x) / dx\n if (t < 0 || t > 1) {\n // Intersection outside the edge segment\n return null\n }\n\n const y = p1.y + t * (p2.y - p1.y)\n return { x: rayX, y }\n}\n","/**\n * Layer alignment — snap same-layer nodes to a uniform position.\n * Split out of layout-engine.ts as a self-contained post-processing pass\n * over a PositionedGraph's nodes/edges.\n */\n\nimport type {\n PositionedNode,\n PositionedEdge,\n Direction,\n} from '@zombie-mermaid/core'\nimport { DEFAULTS } from './constants.ts'\n\n/**\n * ELK's orthogonal edge routing staggers nodes within the same layer to create\n * space for edge bends. This post-processing step groups nodes into layers and\n * snaps them to the same flow-axis coordinate (Y for TD/TB, X for LR/RL).\n *\n * Grouping uses proximity along the flow axis: within a layer, ELK's stagger\n * is always less than layerSpacing (bounded by edge routing channels), while\n * adjacent layers are separated by at least layerSpacing + nodeHeight.\n * A threshold of 0.75 * layerSpacing cleanly separates these cases.\n *\n * Directly connected nodes (sharing an edge) are never merged into the same\n * layer group as an additional safety check.\n *\n * Edge endpoints connected to shifted nodes are adjusted proportionally.\n * Intermediate bend points are left unchanged — edge bundling or clipping\n * will recalculate them afterwards.\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// a bounds-checked array access, each backed by an explicit check earlier\n// in the same scope — `sorted[0]!` and the `sorted[i]!`/`sorted[i - 1]!`\n// loop-index accesses below are guaranteed by the `nodes.length === 0`\n// early return plus the `for (let i = 1; i < sorted.length; ...)` loop\n// bound; `edge.points[0]!`, `edge.points[1]!`, and the\n// `edge.points[edge.points.length - 1]!` / `[edge.points.length - 2]!`\n// pairs are guaranteed by the `edge.points.length < 2` early `continue`\n// (and, redundantly, the `edge.points.length > 1` checks right above each\n// access). This is the same idiom already accepted as justified elsewhere\n// in this codebase (see src/parser.ts, PR #158, and this directory's\n// edge-bundling.ts/from-elk.ts audit, PR #150, which didn't reach this\n// file). `noUncheckedIndexedAccess` can't see any of these guarantees,\n// but removing the `!` would only replace a proven-safe assertion with an\n// unreachable guard. Left as-is; no behavior change.\nexport function alignLayerNodes(\n nodes: PositionedNode[],\n edges: PositionedEdge[],\n direction: Direction,\n): void {\n if (nodes.length === 0) return\n\n const isHorizontal = direction === 'LR' || direction === 'RL'\n\n // Build set of directly-connected node pairs.\n // Nodes connected by an edge must not be merged into the same layer.\n const connectedPairs = new Set<string>()\n for (const edge of edges) {\n connectedPairs.add(`${edge.source}:${edge.target}`)\n connectedPairs.add(`${edge.target}:${edge.source}`)\n }\n\n // ELK's stagger creates small gaps between adjacent nodes in the same layer\n // (typically edgeEdge spacing = 12px per routing channel). Adjacent layers\n // are separated by at least layerSpacing (48px). We use single-linkage\n // clustering: a node joins the current layer if the gap from the previous\n // node (in sorted order) is within threshold, AND it has no direct edge to\n // any node already in the layer.\n const THRESHOLD = DEFAULTS.layerSpacing * 0.6\n\n // Sort nodes by flow-axis position\n const sorted = [...nodes].sort((a, b) =>\n isHorizontal ? a.x - b.x : a.y - b.y,\n )\n\n const layers: PositionedNode[][] = []\n let currentLayer: PositionedNode[] = [sorted[0]!]\n\n for (let i = 1; i < sorted.length; i++) {\n const pos = isHorizontal ? sorted[i]!.x : sorted[i]!.y\n const prevPos = isHorizontal ? sorted[i - 1]!.x : sorted[i - 1]!.y\n // Single-linkage: compare with previous node, not layer start\n const gap = pos - prevPos\n // Check if this node is connected to any node already in the current layer\n const hasEdgeToLayer = currentLayer.some((n) =>\n connectedPairs.has(`${n.id}:${sorted[i]!.id}`),\n )\n if (gap <= THRESHOLD && !hasEdgeToLayer) {\n currentLayer.push(sorted[i]!)\n } else {\n layers.push(currentLayer)\n currentLayer = [sorted[i]!]\n }\n }\n layers.push(currentLayer)\n\n // Snap each layer's nodes to the layer's center position\n const deltas = new Map<string, number>() // nodeId → shift amount\n\n for (const layer of layers) {\n if (layer.length <= 1) continue\n\n const positions = layer.map((n) => (isHorizontal ? n.x : n.y))\n const min = Math.min(...positions)\n const max = Math.max(...positions)\n if (max - min <= 1) continue // Already aligned\n\n // Use the center of the range as the snap target\n const target = (min + max) / 2\n\n for (const node of layer) {\n const oldPos = isHorizontal ? node.x : node.y\n const delta = target - oldPos\n if (Math.abs(delta) > 0.5) {\n if (isHorizontal) {\n node.x = target\n } else {\n node.y = target\n }\n deltas.set(node.id, delta)\n }\n }\n }\n\n if (deltas.size === 0) return\n\n // Adjust edge endpoints to match shifted node positions\n for (const edge of edges) {\n if (edge.points.length < 2) continue\n\n const srcDelta = deltas.get(edge.source)\n const tgtDelta = deltas.get(edge.target)\n\n if (srcDelta != null) {\n // Shift first point and any subsequent points in the initial vertical/horizontal run\n const first = edge.points[0]!\n if (isHorizontal) {\n first.x += srcDelta\n // Shift second point if it's part of a straight vertical exit\n if (\n edge.points.length > 1 &&\n edge.points[1]!.x === first.x - srcDelta\n ) {\n edge.points[1]!.x += srcDelta\n }\n } else {\n first.y += srcDelta\n if (\n edge.points.length > 1 &&\n edge.points[1]!.y === first.y - srcDelta\n ) {\n edge.points[1]!.y += srcDelta\n }\n }\n }\n\n if (tgtDelta != null) {\n const last = edge.points[edge.points.length - 1]!\n if (isHorizontal) {\n last.x += tgtDelta\n if (edge.points.length > 1) {\n const prev = edge.points[edge.points.length - 2]!\n if (prev.x === last.x - tgtDelta) prev.x += tgtDelta\n }\n } else {\n last.y += tgtDelta\n if (edge.points.length > 1) {\n const prev = edge.points[edge.points.length - 2]!\n if (prev.y === last.y - tgtDelta) prev.y += tgtDelta\n }\n }\n }\n }\n}\n","/**\n * Edge bundling — merge fan-out / fan-in edge paths into shared trunks.\n * Split out of layout-engine.ts as a self-contained post-processing pass\n * over a PositionedGraph's nodes/edges/groups.\n */\n\nimport type {\n PositionedEdge,\n PositionedNode,\n PositionedGroup,\n Direction,\n Point,\n} from '@zombie-mermaid/core'\n\n/*\n * Shrink applied to a node box before testing it against a bundled path, in px.\n * A trunk that merely grazes a node's border reads as clean, so only a genuine\n * overlap should disqualify a bundle.\n */\nconst NODE_CLEARANCE = 0.5\n\n/**\n * True when every segment of `points` stays clear of all `nodes`, ignoring the\n * ones named in `skipIds` (an edge's own endpoints, which it must touch).\n *\n * Bundling replaces a routed path with a trunk plus a straight branch to each\n * target. That branch is only safe when it spans the gap between two adjacent\n * layers; a target further away makes it cross every layer in between — and\n * whatever nodes sit in them. The layout engine already routed those edges\n * around the obstacles, so a bundle that would collide is rejected and the\n * original routing kept.\n *\n * Paths here are rectilinear, so each segment is tested as a degenerate\n * rectangle against each node's box.\n */\nfunction pathClearOfNodes(\n points: Point[],\n nodes: PositionedNode[],\n skipIds: Set<string>,\n): boolean {\n for (let i = 0; i < points.length - 1; i++) {\n const a = points[i]!\n const b = points[i + 1]!\n const segMinX = Math.min(a.x, b.x)\n const segMaxX = Math.max(a.x, b.x)\n const segMinY = Math.min(a.y, b.y)\n const segMaxY = Math.max(a.y, b.y)\n\n for (const node of nodes) {\n if (skipIds.has(node.id)) continue\n const minX = node.x + NODE_CLEARANCE\n const maxX = node.x + node.width - NODE_CLEARANCE\n const minY = node.y + NODE_CLEARANCE\n const maxY = node.y + node.height - NODE_CLEARANCE\n // A node smaller than the clearance on either axis can't block anything.\n if (minX >= maxX || minY >= maxY) continue\n if (\n segMinX < maxX &&\n segMaxX > minX &&\n segMinY < maxY &&\n segMaxY > minY\n ) {\n return false\n }\n }\n }\n return true\n}\n\n/**\n * Find all groups (outermost first) that geometrically contain the given point.\n */\nfunction findGroupsContainingPoint(\n x: number,\n y: number,\n groups: PositionedGroup[],\n): PositionedGroup[] {\n const result: PositionedGroup[] = []\n for (const g of groups) {\n if (x >= g.x && x <= g.x + g.width && y >= g.y && y <= g.y + g.height) {\n result.push(g)\n result.push(...findGroupsContainingPoint(x, y, g.children))\n }\n }\n return result\n}\n\n/**\n * If `junction` falls inside a group that doesn't contain the reference node,\n * move it just outside the outermost such group boundary.\n */\nfunction adjustJunctionForGroups(\n junctionMain: number, // the junction coordinate along the flow axis (Y for TD, X for LR)\n refX: number, // reference node center X (for finding its groups)\n refY: number, // reference node center Y\n groups: PositionedGroup[],\n direction: Direction,\n): number {\n const GAP = 12\n const isLR = direction === 'LR'\n const isRL = direction === 'RL'\n const isBT = direction === 'BT'\n const isHorizontal = isLR || isRL\n\n // Groups containing the reference node\n const refGroupIds = new Set(\n findGroupsContainingPoint(refX, refY, groups).map((g) => g.id),\n )\n\n // Check where the junction point would be along the trunk\n const probeX = isHorizontal ? junctionMain : refX\n const probeY = isHorizontal ? refY : junctionMain\n const junctionGroups = findGroupsContainingPoint(probeX, probeY, groups)\n\n // Find outermost group containing the junction but NOT the reference node\n const crossingGroup = junctionGroups.find((g) => !refGroupIds.has(g.id))\n if (!crossingGroup) return junctionMain\n\n // Move junction just outside this group\n if (isLR) return crossingGroup.x - GAP\n if (isRL) return crossingGroup.x + crossingGroup.width + GAP\n if (isBT) return crossingGroup.y + crossingGroup.height + GAP\n return crossingGroup.y - GAP // TD\n}\n\n/**\n * Bundle fan-out and fan-in edge paths so they share a common trunk segment.\n *\n * For fan-out (one source → N targets), all edges exit the source at the same\n * point, travel along a shared trunk, then branch to their individual targets.\n * The overlapping trunk segments render as a single visible line.\n *\n * Junction points are placed outside subgraph boundaries so branches split\n * before entering a group, not inside it.\n *\n * Constraints: edges in a bundle must share the same style and have no labels.\n * Self-loops and backward edges (against the graph direction) are excluded.\n */\nexport function bundleEdgePaths(\n edges: PositionedEdge[],\n nodes: PositionedNode[],\n groups: PositionedGroup[],\n direction: Direction,\n): void {\n const nodeMap = new Map(nodes.map((n) => [n.id, n]))\n const processed = new Set<PositionedEdge>()\n\n const isLR = direction === 'LR'\n const isRL = direction === 'RL'\n const isBT = direction === 'BT'\n const isHorizontal = isLR || isRL\n\n // --- Fan-out: group edges by shared source ---\n const fanOutGroups = new Map<string, PositionedEdge[]>()\n for (const edge of edges) {\n if (edge.source === edge.target) continue\n const group = fanOutGroups.get(edge.source) ?? []\n fanOutGroups.set(edge.source, group)\n group.push(edge)\n }\n\n for (const [sourceId, group] of fanOutGroups) {\n if (group.length < 2) continue\n\n const style = group[0]!.style\n if (group.some((e) => e.label || e.style !== style)) continue\n\n const source = nodeMap.get(sourceId)\n if (!source) continue\n\n // Only bundle edges going in the forward direction. Collects the\n // resolved target node alongside each edge as it filters, rather than\n // re-querying nodeMap afterward — the two lookups can't be proven to\n // agree from a map() over a separately-filtered array.\n const targets: { edge: PositionedEdge; node: PositionedNode }[] = []\n for (const e of group) {\n const t = nodeMap.get(e.target)\n if (!t) continue\n const isForward = isLR\n ? t.x > source.x + source.width\n : isRL\n ? t.x + t.width < source.x\n : isBT\n ? t.y + t.height < source.y\n : t.y > source.y + source.height // TD/TB\n if (isForward) targets.push({ edge: e, node: t })\n }\n if (targets.length < 2) continue\n\n const srcCX = source.x + source.width / 2\n const srcCY = source.y + source.height / 2\n\n if (isHorizontal) {\n const exitX = isLR ? source.x + source.width : source.x\n const exitY = srcCY\n\n const nearestX = isLR\n ? Math.min(...targets.map((t) => t.node.x))\n : Math.max(...targets.map((t) => t.node.x + t.node.width))\n let junctionX = exitX + (nearestX - exitX) / 2\n junctionX = adjustJunctionForGroups(\n junctionX,\n srcCX,\n srcCY,\n groups,\n direction,\n )\n\n const bundled: { edge: PositionedEdge; points: Point[] }[] = []\n for (const { edge, node: target } of targets) {\n const entryX = isLR ? target.x : target.x + target.width\n const entryY = target.y + target.height / 2\n const points = [\n { x: exitX, y: exitY },\n { x: junctionX, y: exitY },\n { x: junctionX, y: entryY },\n { x: entryX, y: entryY },\n ]\n if (pathClearOfNodes(points, nodes, new Set([sourceId, target.id]))) {\n bundled.push({ edge, points })\n }\n }\n // One clear branch is not a bundle — leave the whole group as routed.\n if (bundled.length < 2) continue\n for (const { edge, points } of bundled) {\n edge.points = points\n processed.add(edge)\n }\n } else {\n const exitX = srcCX\n const exitY = isBT ? source.y : source.y + source.height\n\n const nearestY = isBT\n ? Math.max(...targets.map((t) => t.node.y + t.node.height))\n : Math.min(...targets.map((t) => t.node.y))\n let junctionY = exitY + (nearestY - exitY) / 2\n junctionY = adjustJunctionForGroups(\n junctionY,\n srcCX,\n srcCY,\n groups,\n direction,\n )\n\n const bundled: { edge: PositionedEdge; points: Point[] }[] = []\n for (const { edge, node: target } of targets) {\n const entryX = target.x + target.width / 2\n const entryY = isBT ? target.y + target.height : target.y\n const points = [\n { x: exitX, y: exitY },\n { x: exitX, y: junctionY },\n { x: entryX, y: junctionY },\n { x: entryX, y: entryY },\n ]\n if (pathClearOfNodes(points, nodes, new Set([sourceId, target.id]))) {\n bundled.push({ edge, points })\n }\n }\n // One clear branch is not a bundle — leave the whole group as routed.\n if (bundled.length < 2) continue\n for (const { edge, points } of bundled) {\n edge.points = points\n processed.add(edge)\n }\n }\n }\n\n // --- Fan-in: group edges by shared target (skip already-bundled edges) ---\n const fanInGroups = new Map<string, PositionedEdge[]>()\n for (const edge of edges) {\n if (processed.has(edge) || edge.source === edge.target) continue\n const group = fanInGroups.get(edge.target) ?? []\n fanInGroups.set(edge.target, group)\n group.push(edge)\n }\n\n for (const [targetId, group] of fanInGroups) {\n if (group.length < 2) continue\n\n const style = group[0]!.style\n if (group.some((e) => e.label || e.style !== style)) continue\n\n const target = nodeMap.get(targetId)\n if (!target) continue\n\n // Collects the resolved source node alongside each edge as it filters,\n // rather than re-querying nodeMap afterward (see the fan-out loop above\n // for why: a map() over a separately-filtered array can't be proven to\n // agree with the filter's own lookup).\n const sources: { edge: PositionedEdge; node: PositionedNode }[] = []\n for (const e of group) {\n const s = nodeMap.get(e.source)\n if (!s) continue\n const isForward = isLR\n ? s.x + s.width < target.x\n : isRL\n ? s.x > target.x + target.width\n : isBT\n ? s.y > target.y + target.height\n : s.y + s.height < target.y // TD/TB\n if (isForward) sources.push({ edge: e, node: s })\n }\n if (sources.length < 2) continue\n const tgtCX = target.x + target.width / 2\n const tgtCY = target.y + target.height / 2\n\n if (isHorizontal) {\n const entryX = isLR ? target.x : target.x + target.width\n const entryY = tgtCY\n\n const farthestX = isLR\n ? Math.max(...sources.map((s) => s.node.x + s.node.width))\n : Math.min(...sources.map((s) => s.node.x))\n let junctionX = farthestX + (entryX - farthestX) / 2\n junctionX = adjustJunctionForGroups(\n junctionX,\n tgtCX,\n tgtCY,\n groups,\n direction,\n )\n\n const bundled: { edge: PositionedEdge; points: Point[] }[] = []\n for (const { edge, node: src } of sources) {\n const exitX = isLR ? src.x + src.width : src.x\n const exitY = src.y + src.height / 2\n const points = [\n { x: exitX, y: exitY },\n { x: junctionX, y: exitY },\n { x: junctionX, y: entryY },\n { x: entryX, y: entryY },\n ]\n if (pathClearOfNodes(points, nodes, new Set([src.id, targetId]))) {\n bundled.push({ edge, points })\n }\n }\n // One clear branch is not a bundle — leave the whole group as routed.\n if (bundled.length < 2) continue\n for (const { edge, points } of bundled) {\n edge.points = points\n }\n } else {\n const entryX = tgtCX\n const entryY = isBT ? target.y + target.height : target.y\n\n const farthestY = isBT\n ? Math.min(...sources.map((s) => s.node.y))\n : Math.max(...sources.map((s) => s.node.y + s.node.height))\n let junctionY = farthestY + (entryY - farthestY) / 2\n junctionY = adjustJunctionForGroups(\n junctionY,\n tgtCX,\n tgtCY,\n groups,\n direction,\n )\n\n const bundled: { edge: PositionedEdge; points: Point[] }[] = []\n for (const { edge, node: src } of sources) {\n const exitX = src.x + src.width / 2\n const exitY = isBT ? src.y : src.y + src.height\n const points = [\n { x: exitX, y: exitY },\n { x: exitX, y: junctionY },\n { x: entryX, y: junctionY },\n { x: entryX, y: entryY },\n ]\n if (pathClearOfNodes(points, nodes, new Set([src.id, targetId]))) {\n bundled.push({ edge, points })\n }\n }\n // One clear branch is not a bundle — leave the whole group as routed.\n if (bundled.length < 2) continue\n for (const { edge, points } of bundled) {\n edge.points = points\n }\n }\n }\n}\n","/**\n * Shared ELK-edge-geometry extraction helpers.\n *\n * `from-elk.ts` (flowchart/state), `../class/layout.ts`, and\n * `../er/layout.ts` each independently walk an ELK edge's\n * `section.startPoint → bendPoints → endPoint` into a `Point[]`, plus\n * near-identical edge-label-position math. This module is the single\n * place that logic lives, so the three call sites can't drift.\n */\n\nimport type { ElkExtendedEdge } from 'elkjs'\nimport type { Point } from '@zombie-mermaid/core'\n\n/**\n * Walk an ELK edge's first routed section into a flat point path:\n * `startPoint → bendPoints → endPoint`.\n *\n * ELK can theoretically produce multiple sections per edge (for edges\n * split across hierarchy boundaries via ports), but all three call sites\n * only ever read `sections[0]` — hierarchical decomposition in this\n * codebase is handled by emitting separate ELK edges (see\n * `to-elk.ts`/`parseHopEdgeId` in `from-elk.ts`), not multi-section edges.\n *\n * `offsetX`/`offsetY` translate the section's coordinates into an\n * ancestor's coordinate space, for callers walking a nested ELK result\n * (`from-elk.ts`). Callers with a flat (non-hierarchical) ELK graph\n * (`class/layout.ts`, `er/layout.ts`) can omit them.\n *\n * Returns an empty array if the edge has no routed sections.\n */\nexport function extractEdgePoints(\n elkEdge: ElkExtendedEdge,\n offsetX = 0,\n offsetY = 0,\n): Point[] {\n const points: Point[] = []\n if (!elkEdge.sections || elkEdge.sections.length === 0) return points\n\n const section = elkEdge.sections[0]!\n points.push({\n x: section.startPoint.x + offsetX,\n y: section.startPoint.y + offsetY,\n })\n if (section.bendPoints) {\n for (const bp of section.bendPoints) {\n points.push({ x: bp.x + offsetX, y: bp.y + offsetY })\n }\n }\n points.push({\n x: section.endPoint.x + offsetX,\n y: section.endPoint.y + offsetY,\n })\n\n return points\n}\n\n/**\n * Compute an edge label's center position from ELK's placed label box\n * (`label.x/y` is the box's top-left corner).\n *\n * `offsetX`/`offsetY` translate into an ancestor's coordinate space, same\n * as `extractEdgePoints`.\n *\n * Returns `undefined` if ELK didn't place a label (no label on the edge,\n * or ELK left `x`/`y` unset).\n */\nexport function extractEdgeLabelPosition(\n elkEdge: ElkExtendedEdge,\n offsetX = 0,\n offsetY = 0,\n): Point | undefined {\n if (!elkEdge.labels || elkEdge.labels.length === 0) return undefined\n\n const label = elkEdge.labels[0]!\n if (label.x == null || label.y == null) return undefined\n\n return {\n x: label.x + (label.width ?? 0) / 2 + offsetX,\n y: label.y + (label.height ?? 0) / 2 + offsetY,\n }\n}\n","/**\n * Result conversion: ELK output → PositionedGraph.\n *\n * Split out of layout-engine.ts. This module handles turning ELK's layout\n * result back into our PositionedGraph format, including the post-processing\n * passes (layer alignment, edge bundling, shape-aware clipping) that clean\n * up ELK's raw output.\n */\n\nimport type { ElkNode, ElkExtendedEdge } from 'elkjs'\nimport type {\n MermaidGraph,\n MermaidSubgraph,\n PositionedGraph,\n PositionedNode,\n PositionedEdge,\n PositionedGroup,\n Point,\n} from '@zombie-mermaid/core'\nimport { ARROW_HEAD } from '../styles.ts'\nimport { clipEdgeToShape } from '../shape-clipping.ts'\nimport { DEFAULTS } from './constants.ts'\nimport { alignLayerNodes } from './layer-alignment.ts'\nimport { bundleEdgePaths } from './edge-bundling.ts'\nimport {\n extractEdgePoints,\n extractEdgeLabelPosition,\n} from './elk-adapter-utils.ts'\nimport { resolveNodeStyle } from '@zombie-mermaid/core'\n\n/** Margin routing info for cross-hierarchy edges */\ninterface MarginInfo {\n leftX: number\n rightX: number\n}\n\n// ============================================================================\n// Self-loop synthesis\n//\n// ELK has no native self-loop layout — to-elk.ts excludes edges where\n// `source === target` from the graph it hands to ELK entirely (see the\n// comments in `mermaidToElk`/`subgraphToElk`), so they never reach\n// ELK's own edge routing. Once node positions are final, this module\n// synthesizes a rounded loop bulging out from the node's right side —\n// approximated as a half-ellipse discretized into enough points to read\n// as smooth even under the default `linear` (straight-segment) curve\n// style — rather than letting ELK's generic orthogonal router draw a\n// degenerate zero-span polyline on top of the node.\n// ============================================================================\n\n/** Half-ellipse discretization steps; higher reads smoother as straight segments. */\nconst SELF_LOOP_SEGMENTS = 16\n/** Base horizontal reach of the loop bulge, in px. */\nconst SELF_LOOP_BASE_RADIUS = 28\n/** Additional reach per stacked self-loop on the same node, in px. */\nconst SELF_LOOP_STACK_SPACING = 18\n/** Vertical distance between the loop's two attach points, in px (clamped to node height). */\nconst SELF_LOOP_ATTACH_GAP = 28\n/**\n * Gap between the loop's apex and its label, in px. Wide enough that the\n * label's own background box (which paints over whatever it sits on top\n * of) doesn't swallow the loop's visible curve.\n */\nconst SELF_LOOP_LABEL_GAP = 10\n\n/**\n * Build the routed points and label position for one self-loop edge on\n * `node`, as a half-ellipse bulging out from the node's right side.\n *\n * `stackIndex` (0 for the first self-loop on this node, 1 for the second,\n * ...) widens each successive loop so multiple self-loops on the same node\n * nest rather than overlap.\n */\nfunction buildSelfLoopGeometry(\n node: PositionedNode,\n stackIndex: number,\n): { points: Point[]; labelPosition: Point } {\n const attachX = node.x + node.width\n const midY = node.y + node.height / 2\n const halfGap = Math.min(SELF_LOOP_ATTACH_GAP, node.height * 0.6) / 2\n const radius = SELF_LOOP_BASE_RADIUS + stackIndex * SELF_LOOP_STACK_SPACING\n\n // Half-ellipse from the upper attach point (t=0), out to the apex\n // (t=PI/2), back to the lower attach point (t=PI). Both attach points\n // sit exactly on the node's right edge, so shape-clipping (a no-op for\n // rectangle/rounded/stadium, the common cases) leaves them untouched.\n const points: Point[] = []\n for (let i = 0; i <= SELF_LOOP_SEGMENTS; i++) {\n const t = (Math.PI * i) / SELF_LOOP_SEGMENTS\n points.push({\n x: attachX + radius * Math.sin(t),\n y: midY - halfGap * Math.cos(t),\n })\n }\n\n return {\n points,\n labelPosition: { x: attachX + radius + SELF_LOOP_LABEL_GAP, y: midY },\n }\n}\n\n/**\n * Append a synthesized `PositionedEdge` for every self-loop\n * (`edge.source === edge.target`) in `graph.edges` onto `edges`.\n *\n * Must run after node positions are final (i.e. after `alignLayerNodes`) —\n * the loop geometry is derived directly from the node's laid-out\n * x/y/width/height, so computing it any earlier would have it drift out of\n * sync with a node that layer-alignment later shifts.\n */\nfunction synthesizeSelfLoopEdges(\n graph: MermaidGraph,\n nodeMap: Map<string, PositionedNode>,\n edges: PositionedEdge[],\n): void {\n const stackIndexByNode = new Map<string, number>()\n\n for (let index = 0; index < graph.edges.length; index++) {\n const edge = graph.edges[index]!\n if (edge.source !== edge.target) continue\n\n const node = nodeMap.get(edge.source)\n if (!node) continue\n\n const stackIndex = stackIndexByNode.get(node.id) ?? 0\n stackIndexByNode.set(node.id, stackIndex + 1)\n\n const { points, labelPosition } = buildSelfLoopGeometry(node, stackIndex)\n\n edges.push({\n source: edge.source,\n target: edge.target,\n label: edge.label,\n style: edge.style,\n hasArrowStart: edge.hasArrowStart,\n hasArrowEnd: edge.hasArrowEnd,\n points,\n labelPosition: edge.label ? labelPosition : undefined,\n inlineStyle: resolveEdgeStyle(index, graph),\n id: edge.id,\n animate: edge.animate,\n })\n }\n}\n\n/** Recursively flatten all group bounding boxes (including nested children) */\nfunction flattenGroupBounds(\n groups: PositionedGroup[],\n): Array<{ x: number; y: number; right: number; bottom: number }> {\n const bounds: Array<{ x: number; y: number; right: number; bottom: number }> =\n []\n for (const g of groups) {\n bounds.push({\n x: g.x,\n y: g.y,\n right: g.x + g.width,\n bottom: g.y + g.height,\n })\n bounds.push(...flattenGroupBounds(g.children))\n }\n return bounds\n}\n\n/**\n * Convert ELK layout result to our PositionedGraph format.\n */\nexport function elkToPositioned(\n elkResult: ElkNode,\n graph: MermaidGraph,\n mergeEdges: boolean = false,\n): PositionedGraph {\n const nodes: PositionedNode[] = []\n const edges: PositionedEdge[] = []\n const groups: PositionedGroup[] = []\n\n // Build set of subgraph IDs for distinguishing compound nodes from leaf nodes\n const subgraphIds = new Set<string>()\n for (const sg of graph.subgraphs) {\n collectAllSubgraphIds(sg, subgraphIds)\n }\n\n // Extract nodes and groups recursively\n extractNodesAndGroups(elkResult, graph, subgraphIds, nodes, groups, 0, 0)\n\n // Compute margin positions for cross-hierarchy edge routing.\n // Margins sit outside all group bounding boxes so edges don't cross through subgraphs.\n const allBounds = flattenGroupBounds(groups)\n const margins: MarginInfo | undefined =\n allBounds.length > 0\n ? {\n leftX: Math.min(...allBounds.map((b) => b.x)) - 20,\n rightX: Math.max(...allBounds.map((b) => b.right)) + 20,\n }\n : undefined\n\n // Extract edges recursively from all levels (root and subgraphs)\n // Edges are distributed to subgraphs for direction override to work,\n // so we need to collect them from all children with proper offsets\n extractEdgesRecursively(elkResult, graph, edges, 0, 0, margins)\n\n // Snap same-layer nodes to the same position along the flow axis.\n // ELK's orthogonal routing staggers nodes within a layer to create room for\n // edge bends, but this looks bad. We fix it by aligning layers, then let\n // edge bundling and clipping recalculate edge paths from corrected positions.\n alignLayerNodes(nodes, edges, graph.direction)\n\n // Synthesize self-loop edges now that node positions are final. These\n // were excluded from the graph handed to ELK (see to-elk.ts) since ELK\n // has no native self-loop layout; must run after alignLayerNodes so the\n // loop geometry is derived from each node's final, not pre-alignment,\n // position.\n const nodeMap = new Map(nodes.map((n) => [n.id, n]))\n synthesizeSelfLoopEdges(graph, nodeMap, edges)\n\n // Bundle fan-out/fan-in edge paths into shared trunks when mergeEdges is enabled\n if (mergeEdges) {\n bundleEdgePaths(edges, nodes, groups, graph.direction)\n }\n\n // Apply shape-aware edge clipping for non-rectangular shapes.\n // ELK treats all nodes as rectangles, so we need to clip edge endpoints\n // to the actual shape boundaries (e.g., diamond vertices).\n for (const edge of edges) {\n const sourceNode = nodeMap.get(edge.source)\n const targetNode = nodeMap.get(edge.target)\n\n if (sourceNode) {\n edge.points = clipEdgeToShape(edge.points, sourceNode, true)\n }\n if (targetNode) {\n edge.points = clipEdgeToShape(edge.points, targetNode, false)\n }\n }\n\n // Calculate final bounds including all edge points\n // ELK should include edges in its dimensions, but we verify and expand if needed\n let width = elkResult.width ?? 800\n let height = elkResult.height ?? 600\n const arrowMargin = ARROW_HEAD.width\n const padding = DEFAULTS.padding\n\n for (const edge of edges) {\n for (const p of edge.points) {\n width = Math.max(width, p.x + arrowMargin + padding)\n height = Math.max(height, p.y + arrowMargin + padding)\n }\n if (edge.labelPosition) {\n width = Math.max(width, edge.labelPosition.x + 60 + padding)\n height = Math.max(height, edge.labelPosition.y + 20 + padding)\n }\n }\n\n return {\n width,\n height,\n nodes,\n edges,\n groups,\n }\n}\n\n/**\n * Recursively extract positioned nodes and groups from ELK result.\n */\nfunction extractNodesAndGroups(\n elkNode: ElkNode,\n graph: MermaidGraph,\n subgraphIds: Set<string>,\n nodes: PositionedNode[],\n groups: PositionedGroup[],\n offsetX: number,\n offsetY: number,\n): void {\n if (!elkNode.children) return\n\n for (const child of elkNode.children) {\n const x = (child.x ?? 0) + offsetX\n const y = (child.y ?? 0) + offsetY\n const width = child.width ?? 0\n const height = child.height ?? 0\n\n if (subgraphIds.has(child.id)) {\n // This is a subgraph/group\n const childGroups: PositionedGroup[] = []\n\n // Recursively process children\n extractNodesAndGroups(child, graph, subgraphIds, nodes, childGroups, x, y)\n\n const mermaidSg = findSubgraph(graph.subgraphs, child.id)\n groups.push({\n id: child.id,\n label: mermaidSg?.label ?? '',\n x,\n y,\n width,\n height,\n children: childGroups,\n })\n } else {\n // This is a leaf node\n const mNode = graph.nodes.get(child.id)\n if (mNode) {\n // Resolve inline styles from nodeStyles map and classDefs\n const inlineStyle = resolveNodeStyle(child.id, graph)\n // Custom class name from `class A className` or `:::className` —\n // tracked separately from inlineStyle so it's still emitted onto the\n // rendered element even when the class has no matching classDef.\n const className = graph.classAssignments.get(child.id)\n\n nodes.push({\n id: child.id,\n label: mNode.label,\n shape: mNode.shape,\n x,\n y,\n width,\n height,\n inlineStyle,\n className,\n interaction: graph.interactions.get(child.id),\n })\n }\n\n // Also check for nested children (shouldn't happen for leaf nodes, but be safe)\n if (child.children && child.children.length > 0) {\n extractNodesAndGroups(child, graph, subgraphIds, nodes, groups, x, y)\n }\n }\n }\n}\n\n/**\n * Edge segment extracted from ELK result.\n * Used to combine the bridge and hop segments of a decomposed\n * cross-hierarchy edge back into one continuous path.\n */\ninterface EdgeSegment {\n edgeIndex: number\n points: Point[]\n labelPosition?: Point\n}\n\n/**\n * All segments belonging to one original edge, keyed by role:\n * - `bridge`: the sub-edge at the lowest common ancestor of source/target\n * (id `e{index}`) — the only segment for edges that don't cross a\n * subgraph boundary at all.\n * - `sourceHops`: source-side boundary crossings (id `e{index}_s{level}`),\n * keyed by ancestor-chain level. Walking from the innermost level\n * (closest to the source node) outward to the LCA.\n * - `targetHops`: target-side boundary crossings (id `e{index}_t{level}`),\n * keyed by ancestor-chain level. Walking from the LCA inward to the\n * innermost level (closest to the target node).\n * See packages/svg-renderer/src/layout-engine/to-elk.ts (mermaidToElk) for how these are produced.\n */\ninterface EdgeSegmentGroup {\n bridge?: EdgeSegment\n sourceHops: Map<number, EdgeSegment>\n targetHops: Map<number, EdgeSegment>\n}\n\n/** Parses an ELK edge id produced by the cross-hierarchy decomposition in to-elk.ts. */\nfunction parseHopEdgeId(\n id: string,\n):\n | { edgeIndex: number; role: 'bridge' }\n | { edgeIndex: number; role: 'source' | 'target'; level: number }\n | undefined {\n const bridgeMatch = /^e(\\d+)$/.exec(id)\n if (bridgeMatch) {\n return { edgeIndex: parseInt(bridgeMatch[1]!, 10), role: 'bridge' }\n }\n const hopMatch = /^e(\\d+)_([st])(\\d+)$/.exec(id)\n if (hopMatch) {\n return {\n edgeIndex: parseInt(hopMatch[1]!, 10),\n role: hopMatch[2] === 's' ? 'source' : 'target',\n level: parseInt(hopMatch[3]!, 10),\n }\n }\n return undefined\n}\n\n/**\n * Calculate the midpoint along a polyline path.\n * Walks the path to find the point at half the total length.\n */\nfunction calculatePathMidpoint(points: Point[]): Point {\n if (points.length === 0) return { x: 0, y: 0 }\n if (points.length === 1) return points[0]!\n\n // Calculate total length\n let totalLength = 0\n for (let i = 1; i < points.length; i++) {\n const dx = points[i]!.x - points[i - 1]!.x\n const dy = points[i]!.y - points[i - 1]!.y\n totalLength += Math.sqrt(dx * dx + dy * dy)\n }\n\n // Walk to halfway point\n let remaining = totalLength / 2\n for (let i = 1; i < points.length; i++) {\n const dx = points[i]!.x - points[i - 1]!.x\n const dy = points[i]!.y - points[i - 1]!.y\n const segLen = Math.sqrt(dx * dx + dy * dy)\n if (remaining <= segLen) {\n const t = remaining / segLen\n return {\n x: points[i - 1]!.x + t * dx,\n y: points[i - 1]!.y + t * dy,\n }\n }\n remaining -= segLen\n }\n\n return points[points.length - 1]!\n}\n\n/**\n * Recursively extract edges from ELK result including those inside subgraphs.\n * Edges are distributed to subgraphs for direction override to work,\n * so we need to collect them from all levels with proper coordinate offsets.\n *\n * For hierarchical edges (cross-hierarchy with ports), combines external and\n * internal segments into a single continuous edge path.\n */\nfunction extractEdgesRecursively(\n elkNode: ElkNode,\n graph: MermaidGraph,\n edges: PositionedEdge[],\n offsetX: number,\n offsetY: number,\n margins?: MarginInfo,\n): void {\n // First pass: collect all edge segments.\n //\n // Under `hierarchyHandling: INCLUDE_CHILDREN` (see to-elk.ts — used when no\n // subgraph has a direction override), ELK is free to leave a\n // cross-hierarchy edge in an *ancestor* node's `edges` array while\n // reporting its sections/labels in the coordinate space of the deeper\n // container named by the edge's own (untyped-in-elkjs) `container`\n // property. `collectEdgeSegments`'s tree walk assumes an edge's\n // coordinates match the array it was found in, which breaks for exactly\n // this case — the edge lands at the *ancestor's* offset instead of the\n // declared container's, shifting nested-subgraph edges/labels off their\n // node boundaries toward the root. Pre-computing every container's\n // accumulated root offset here lets `collectEdgeSegments` prefer the\n // edge's declared container when present, falling back to the owning\n // array's offset (the previous behavior) otherwise — see\n // zombie-mermaid#1145, ported from a fix by Galen Suen in\n // lukilabs/beautiful-mermaid#152.\n const containerOffsets = new Map<string, Point>()\n collectContainerOffsets(elkNode, containerOffsets, 0, 0)\n const segments = new Map<number, EdgeSegmentGroup>()\n collectEdgeSegments(elkNode, segments, 0, 0, containerOffsets)\n\n // Track margin-routed edge count for spacing offsets\n let marginEdgeIndex = 0\n\n // Second pass: combine segments and create positioned edges\n for (const [edgeIndex, seg] of segments) {\n const originalEdge = graph.edges[edgeIndex]\n if (!originalEdge) continue\n\n // Combine points from all segments in path order:\n // source-side hops (innermost → outermost, i.e. descending level)\n // → bridge (the sub-edge at the lowest common ancestor)\n // → target-side hops (outermost → innermost, i.e. ascending level)\n // Edges that don't cross any subgraph boundary have only a bridge\n // segment. Edges crossing exactly one boundary have a bridge plus a\n // single hop on one side — this is the shape the previous single-hop\n // implementation always produced. Deeper nesting simply adds more hops\n // on either side, each joined at the shared boundary point.\n const allPoints: Point[] = []\n const appendSegment = (segment: EdgeSegment | undefined): void => {\n if (!segment || segment.points.length === 0) return\n if (allPoints.length > 0) {\n // Skip first point to avoid duplicating the shared boundary point.\n allPoints.push(...segment.points.slice(1))\n } else {\n allPoints.push(...segment.points)\n }\n }\n\n const sourceLevels = [...seg.sourceHops.keys()].sort((a, b) => b - a)\n for (const level of sourceLevels) appendSegment(seg.sourceHops.get(level))\n appendSegment(seg.bridge)\n const targetLevels = [...seg.targetHops.keys()].sort((a, b) => a - b)\n for (const level of targetLevels) appendSegment(seg.targetHops.get(level))\n\n // Label position: use ELK's inline label position (on-edge with collision avoidance)\n // Fall back to midpoint for hierarchical edges or when ELK position unavailable\n let labelPosition: Point | undefined\n if (originalEdge.label && allPoints.length >= 2) {\n const elkLabelPos = seg.bridge?.labelPosition\n labelPosition = elkLabelPos ?? calculatePathMidpoint(allPoints)\n }\n\n // Ensure all edge segments are orthogonal (horizontal or vertical only).\n // In SEPARATE hierarchy mode, ELK may produce diagonal segments for\n // cross-hierarchy edges where it only returns start/end points without\n // proper orthogonal bend points.\n // When margins are available, route through the diagram margins instead\n // of Z-paths through the middle (which cross through subgraphs).\n const orthogonalPoints = orthogonalizeEdgePoints(\n allPoints,\n margins,\n marginEdgeIndex,\n )\n if (orthogonalPoints !== allPoints) {\n marginEdgeIndex++\n }\n\n // Recalculate label position for margin-routed edges\n if (\n originalEdge.label &&\n orthogonalPoints !== allPoints &&\n orthogonalPoints.length >= 2\n ) {\n labelPosition = calculatePathMidpoint(orthogonalPoints)\n }\n\n edges.push({\n source: originalEdge.source,\n target: originalEdge.target,\n label: originalEdge.label,\n style: originalEdge.style,\n hasArrowStart: originalEdge.hasArrowStart,\n hasArrowEnd: originalEdge.hasArrowEnd,\n points: orthogonalPoints,\n labelPosition,\n inlineStyle: resolveEdgeStyle(edgeIndex, graph),\n id: originalEdge.id,\n animate: originalEdge.animate,\n })\n }\n}\n\n/**\n * Build a map from every ELK node's own id to its accumulated root-relative\n * offset, by walking the same `elkNode.children` tree `collectEdgeSegments`\n * walks. Used to resolve a cross-hierarchy edge's declared `container`\n * (an ELK output property not modeled in elkjs's types) to the offset of\n * the container ELK actually reported its coordinates in, rather than the\n * offset of whichever ancestor array the edge object happened to be left\n * in — see the comment on `extractEdgesRecursively`'s call site.\n */\nfunction collectContainerOffsets(\n elkNode: ElkNode,\n offsets: Map<string, Point>,\n offsetX: number,\n offsetY: number,\n): void {\n offsets.set(elkNode.id, { x: offsetX, y: offsetY })\n for (const child of elkNode.children ?? []) {\n // `x`/`y` are optional in elkjs's types, but ELK always assigns both to\n // every laid-out child node — the `?? 0` fallback mirrors the same\n // defensive pattern already used for this walk in `collectEdgeSegments`\n // below, and is equally unreachable from real ELK output.\n /* v8 ignore next */\n const childX = child.x ?? 0\n /* v8 ignore next */\n const childY = child.y ?? 0\n collectContainerOffsets(child, offsets, offsetX + childX, offsetY + childY)\n }\n}\n\n/**\n * Post-process edge points to ensure all segments are purely orthogonal.\n *\n * When ELK uses SEPARATE hierarchy handling (required for subgraph direction\n * overrides), cross-hierarchy edges may only get start/end coordinates without\n * intermediate bend points, producing diagonal lines.\n *\n * When margins are provided, routes diagonal segments through the left or right\n * margin of the diagram (outside all subgraphs). Alternates sides and adds\n * spacing offsets to prevent overlapping parallel edges.\n *\n * Without margins, falls back to Z-path through the vertical midpoint.\n *\n * Returns the original array reference (identity) if no changes were needed,\n * so callers can detect whether routing was applied.\n */\nfunction orthogonalizeEdgePoints(\n points: Point[],\n margins?: MarginInfo,\n edgeIndex: number = 0,\n): Point[] {\n if (points.length < 2) return points\n\n // Check if any segment needs orthogonalization\n let needsWork = false\n for (let i = 1; i < points.length; i++) {\n const dx = Math.abs(points[i]!.x - points[i - 1]!.x)\n const dy = Math.abs(points[i]!.y - points[i - 1]!.y)\n if (dx > 1 && dy > 1) {\n needsWork = true\n break\n }\n }\n if (!needsWork) return points\n\n const EDGE_SPACING = 12\n const result: Point[] = [points[0]!]\n\n for (let i = 1; i < points.length; i++) {\n const prev = result[result.length - 1]!\n const curr = points[i]!\n const dx = Math.abs(curr.x - prev.x)\n const dy = Math.abs(curr.y - prev.y)\n\n if (dx > 1 && dy > 1) {\n if (margins) {\n // Margin routing: exit horizontally → travel vertically along margin → enter horizontally\n // Alternate left/right margins and offset for parallel edge spacing\n const useRight = edgeIndex % 2 === 0\n const offset = Math.floor(edgeIndex / 2) * EDGE_SPACING\n const marginX = useRight\n ? margins.rightX + offset\n : margins.leftX - offset\n\n result.push({ x: marginX, y: prev.y })\n result.push({ x: marginX, y: curr.y })\n } else {\n // Fallback: Z-path through vertical midpoint\n const midY = (prev.y + curr.y) / 2\n result.push({ x: prev.x, y: midY })\n result.push({ x: curr.x, y: midY })\n }\n }\n\n result.push(curr)\n }\n\n return result\n}\n\n/**\n * Recursively collect edge segments from ELK result.\n */\nfunction collectEdgeSegments(\n elkNode: ElkNode,\n segments: Map<number, EdgeSegmentGroup>,\n offsetX: number,\n offsetY: number,\n containerOffsets: Map<string, Point>,\n): void {\n if (elkNode.edges) {\n for (const elkEdge of elkNode.edges) {\n // Parse edge ID: \"e{index}\" (bridge), \"e{index}_s{level}\" (source-side\n // hop), or \"e{index}_t{level}\" (target-side hop) — see to-elk.ts.\n const parsed = parseHopEdgeId(elkEdge.id)\n if (!parsed) continue\n const { edgeIndex } = parsed\n\n // Prefer the edge's declared container offset (see\n // `collectContainerOffsets`'s comment) over the owning array's\n // offset. `container` and `containerOffsets.get(container)` were\n // verified (by direct instrumentation against real elk.bundled.js\n // output, across both flat and multiply-nested subgraphs) to always\n // be defined in practice — ELK tags every edge with its container,\n // even at the root, and `collectContainerOffsets` walks the same\n // tree so every container id it can name is already in the map. The\n // `container?`/`?? offset{X,Y}` fallbacks below exist only because\n // `container` is untyped in elkjs and `Map.get` is typed to allow a\n // miss; kept as defensive narrowing rather than assumed impossible.\n const container = (elkEdge as ElkExtendedEdge & { container?: string })\n .container\n /* v8 ignore next */\n const containerOffset = container\n ? containerOffsets.get(container)\n : undefined\n /* v8 ignore next */\n const edgeOffsetX = containerOffset?.x ?? offsetX\n /* v8 ignore next */\n const edgeOffsetY = containerOffset?.y ?? offsetY\n\n // Extract points and label position\n const points = extractEdgePoints(elkEdge, edgeOffsetX, edgeOffsetY)\n const labelPosition = extractEdgeLabelPosition(\n elkEdge,\n edgeOffsetX,\n edgeOffsetY,\n )\n\n // Store segment\n const seg: EdgeSegmentGroup = segments.get(edgeIndex) ?? {\n sourceHops: new Map(),\n targetHops: new Map(),\n }\n segments.set(edgeIndex, seg)\n const segment: EdgeSegment = { edgeIndex, points, labelPosition }\n\n if (parsed.role === 'bridge') {\n seg.bridge = segment\n } else if (parsed.role === 'source') {\n seg.sourceHops.set(parsed.level, segment)\n } else {\n seg.targetHops.set(parsed.level, segment)\n }\n }\n }\n\n // Recurse into children with accumulated offset\n if (elkNode.children) {\n for (const child of elkNode.children) {\n collectEdgeSegments(\n child,\n segments,\n offsetX + (child.x ?? 0),\n offsetY + (child.y ?? 0),\n containerOffsets,\n )\n }\n }\n}\n\n/** Find a subgraph by ID in a nested structure */\nfunction findSubgraph(\n subgraphs: MermaidSubgraph[],\n id: string,\n): MermaidSubgraph | undefined {\n for (const sg of subgraphs) {\n if (sg.id === id) return sg\n const found = findSubgraph(sg.children, id)\n if (found) return found\n }\n return undefined\n}\n\n/** Recursively collect all subgraph IDs */\nfunction collectAllSubgraphIds(sg: MermaidSubgraph, out: Set<string>): void {\n out.add(sg.id)\n for (const child of sg.children) {\n collectAllSubgraphIds(child, out)\n }\n}\n\n/**\n * Resolve inline styles for an edge from linkStyles map.\n * Default link style is applied first, then index-specific overrides.\n */\nfunction resolveEdgeStyle(\n edgeIndex: number,\n graph: MermaidGraph,\n): Record<string, string> | undefined {\n let result: Record<string, string> | undefined\n\n const defaultStyle = graph.linkStyles.get('default')\n if (defaultStyle) {\n result = { ...defaultStyle }\n }\n\n const indexStyle = graph.linkStyles.get(edgeIndex)\n if (indexStyle) {\n result = result ? { ...result, ...indexStyle } : { ...indexStyle }\n }\n\n return result\n}\n","/**\n * Layout engine for zombie-mermaid (ELK.js based).\n *\n * Converts MermaidGraph to ELK's JSON format, runs layout, and converts\n * the result back to PositionedGraph. This is the core layout engine used\n * by all graph-based diagram types (flowcharts, state, ER, class).\n *\n * ELK (Eclipse Layout Kernel) features:\n * - Native orthogonal edge routing (no post-processing needed)\n * - Proper handling of compound nodes (subgraphs)\n * - Support for disconnected graphs\n * - Direction overrides per subgraph\n * - Sophisticated algorithms for complex graphs\n *\n * Uses elk.bundled.js (pure synchronous JS, no WASM/Workers).\n * Safe for Electron, Node, and browser environments.\n *\n * This file is the public entry point (layoutGraphSync, convertToElkFormat).\n * The implementation is split across src/layout-engine/ by pipeline stage:\n * - to-elk.ts MermaidGraph → ELK JSON input\n * - from-elk.ts ELK result → PositionedGraph, plus shape clipping\n * - layer-alignment.ts post-process: snap same-layer nodes to a uniform position\n * - edge-bundling.ts post-process: merge fan-out/fan-in edges into shared trunks\n * See packages/svg-renderer/src/layout-engine/constants.ts for the shared DEFAULTS used across stages.\n */\n\nimport type { ElkNode } from 'elkjs'\nimport type {\n MermaidGraph,\n PositionedGraph,\n FlowchartRenderOptions,\n} from '@zombie-mermaid/core'\nimport { elkLayoutSync } from './elk-instance.ts'\nimport { resolveFontSizes } from './styles.ts'\nimport { DEFAULTS } from './layout-engine/constants.ts'\nimport { mermaidToElk } from './layout-engine/to-elk.ts'\nimport { elkToPositioned } from './layout-engine/from-elk.ts'\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Lay out a parsed MermaidGraph using ELK.js (synchronous).\n * Returns a fully positioned graph ready for rendering.\n */\nexport function layoutGraphSync(\n graph: MermaidGraph,\n options: FlowchartRenderOptions = {},\n): PositionedGraph {\n const opts = {\n ...DEFAULTS,\n ...options,\n fontSizes: resolveFontSizes(options.fontSizes),\n }\n const elkGraph = mermaidToElk(graph, opts)\n const result = elkLayoutSync(elkGraph, options.layoutCache)\n return elkToPositioned(result, graph, opts.mergeEdges)\n}\n\n/**\n * Convert MermaidGraph to ELK format (for benchmarking conversion overhead).\n */\nexport function convertToElkFormat(\n graph: MermaidGraph,\n options: FlowchartRenderOptions = {},\n): ElkNode {\n const opts = {\n ...DEFAULTS,\n ...options,\n fontSizes: resolveFontSizes(options.fontSizes),\n }\n return mermaidToElk(graph, opts)\n}\n","import type {\n PositionedGraph,\n PositionedNode,\n PositionedEdge,\n PositionedGroup,\n Point,\n DiagramColors,\n SvgEmitOptions,\n CurveStyle,\n} from '@zombie-mermaid/core'\nimport {\n svgOpenTag,\n buildStyleBlock,\n styleOpenTag,\n getReadableTextColor,\n measureMultilineText,\n renderMultilineText,\n renderMultilineTextWithBackground,\n escapeXml,\n escapeAttr,\n safeHref,\n sanitizeClassName,\n} from '@zombie-mermaid/core'\nimport type { FontSizes } from './styles.ts'\nimport {\n FONT_SIZES,\n FONT_WEIGHTS,\n STROKE_WIDTHS,\n ARROW_HEAD,\n} from './styles.ts'\nimport { pointsToPath } from './edge-curves.ts'\n\n// ============================================================================\n// SVG renderer — converts a PositionedGraph into an SVG string.\n//\n// Pure string concatenation, no DOM manipulation.\n// Renders back-to-front: groups → edges → arrow heads → edge labels → nodes → node labels.\n//\n// All colors are referenced via CSS custom properties (var(--_xxx)) defined\n// in the <style> block. The caller provides bg/fg (+ optional enrichment\n// colors) via DiagramColors, which are set as inline CSS variables on the\n// <svg> tag. See packages/core/src/theme.ts for the full variable system.\n//\n// Style spec:\n// - All corners rx=0 ry=0 (sharp)\n// - Stroke widths: outer box 1px, inner box 0.75px, connectors 0.75px\n// - Arrow heads: filled triangles, 8px wide × 4.8px tall\n// - Dashed edges: stroke-dasharray=\"4 4\"\n// - Font: Inter with weight per element type\n// ============================================================================\n\n/**\n * Render a positioned graph as an SVG string.\n *\n * @param colors - DiagramColors with bg/fg and optional enrichment variables.\n * These are set as CSS custom properties on the <svg> tag.\n * All element colors reference derived --_xxx variables.\n * @param transparent - If true, renders with transparent background.\n * @param embedSource - Original diagram source to stamp onto the root `<svg>`\n * as `data-src` (from `options.embedSource`). Omitted\n * when the option is off.\n * @param animationEnabled - Whether `e1@{ animate: true }` edges actually\n * animate (from `options.interactivity === 'full'`,\n * see `resolveAnimationEnabled` in\n * src/diagram-registry.ts).\n * Default true — preserves the previously-ungated\n * behavior for callers who don't pass it.\n * @param linksEnabled - Whether `click`-based `<a href>` links and `<title>`\n * tooltips render (from\n * `options.interactivity !== 'none'`, see\n * `resolveLinksEnabled` in src/diagram-registry.ts). Default true\n * — preserves the previously-ungated behavior for\n * callers who don't pass it.\n * @param title - Accessible name (from `options.title`). See svgOpenTag() in\n * packages/core/src/theme.ts.\n * @param decorative - Marks the SVG decorative (from `options.decorative`).\n * @param emit - Strict-CSP controls (from `options.nonce` /\n * `options.styleAttribute`, see #216): a `nonce` for every\n * `<style>` element, and whether the root `style=\"…\"`\n * attribute is emitted at all. Default: no nonce, attribute on.\n */\nexport function renderSvg(\n graph: PositionedGraph,\n colors: DiagramColors,\n font: string = 'Inter',\n transparent: boolean = false,\n fontSizes: FontSizes = FONT_SIZES,\n curve: CurveStyle = 'linear',\n embedSource?: string,\n animationEnabled: boolean = true,\n linksEnabled: boolean = true,\n title?: string,\n decorative?: boolean,\n emit: SvgEmitOptions = {},\n): string {\n const parts: string[] = []\n\n // See #239: a click-based link renders as a focusable <a href> inside the\n // SVG, which role=\"img\"/aria-hidden would hide from assistive tech while\n // leaving it Tab-reachable — svgOpenTag forces no root role in that case.\n // Uses the same safeHref() check renderNode uses to decide whether an <a>\n // is actually emitted — a rejected scheme or control character means no\n // link renders, so it shouldn't affect the SVG's accessibility semantics.\n // Gated by linksEnabled too: `interactivity: 'none'` strips the <a> below,\n // so it shouldn't count toward this decision either.\n const hasInteractiveLinks =\n linksEnabled &&\n graph.nodes.some((n) => Boolean(safeHref(n.interaction?.href)))\n\n // SVG root with CSS variables + style block + defs\n parts.push(\n withDataSrc(\n svgOpenTag(\n graph.width,\n graph.height,\n colors,\n transparent,\n title,\n decorative,\n hasInteractiveLinks,\n emit.styleAttribute,\n ),\n embedSource,\n ),\n )\n parts.push(buildStyleBlock(font, false, emit.nonce))\n // Keyframes for animated edges (`e1@{ animate: true }`). Emitted only when\n // an animated edge exists and animation is enabled, so an ordinary diagram\n // (or one rendered with `interactivity: 'none'`) gains no extra markup.\n if (animationEnabled && graph.edges.some((e) => e.animate)) {\n parts.push(edgeAnimationStyle(emit.nonce))\n }\n parts.push('<defs>')\n parts.push(arrowMarkerDefs())\n // Per-color arrow markers for edges with custom stroke via linkStyle\n const customStrokeColors = new Set<string>()\n for (const edge of graph.edges) {\n if (edge.inlineStyle?.stroke) {\n customStrokeColors.add(edge.inlineStyle.stroke)\n }\n }\n for (const color of customStrokeColors) {\n parts.push(arrowMarkerDefsForColor(color))\n }\n parts.push('</defs>')\n\n // 1. Subgraph backgrounds (group rectangles with header bands)\n for (const group of graph.groups) {\n parts.push(renderGroup(group, font, fontSizes))\n }\n\n // 2. Edges (paths — rendered behind nodes)\n // Each edge is a <path> with semantic data-* attributes\n for (const edge of graph.edges) {\n parts.push(renderEdge(edge, curve, animationEnabled))\n }\n\n // 3. Edge labels (positioned at midpoint of edge)\n // Each label is wrapped in <g class=\"edge-label\">\n for (const edge of graph.edges) {\n if (edge.label) {\n parts.push(renderEdgeLabel(edge, font, fontSizes))\n }\n }\n\n // 4. Nodes (shape + label wrapped in <g class=\"node\">)\n for (const node of graph.nodes) {\n parts.push(renderNode(node, font, fontSizes, linksEnabled))\n }\n\n parts.push('</svg>')\n\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Arrow marker definitions\n// ============================================================================\n\n/**\n * Keyframes for `e1@{ animate: true }` edges — a marching-ants dash.\n *\n * CSS animation rather than SMIL: SMIL is deprecated in browsers, while a\n * CSS `@keyframes` block inside the SVG animates in browsers and is simply\n * ignored by static rasterizers, which render the first frame. The\n * `prefers-reduced-motion` guard stops the animation for users who have asked\n * the system for less movement — the edge still renders, just still.\n *\n * A second `<style>` element, so it takes the same `nonce` as the theme\n * block (see `styleOpenTag`) — under a nonce-based CSP it would otherwise be\n * the one un-nonced element on the page and get silently dropped.\n */\nfunction edgeAnimationStyle(nonce?: string): string {\n return [\n styleOpenTag(nonce),\n ' @keyframes zm-edge-dash { to { stroke-dashoffset: -28; } }',\n ' .edge-animated { animation: zm-edge-dash 1s linear infinite; }',\n ' @media (prefers-reduced-motion: reduce) {',\n ' .edge-animated { animation: none; }',\n ' }',\n '</style>',\n ].join('\\n')\n}\n\n/**\n * Reusable arrow head markers — both forward (end) and reverse (start) variants.\n * Arrow color uses the var(--_arrow) CSS variable.\n */\nfunction arrowMarkerDefs(): string {\n return arrowMarkerPair('var(--_arrow)', '')\n}\n\n/**\n * Arrow markers tinted to a specific color (for linkStyle stroke overrides).\n * IDs are suffixed with a sanitized color string to avoid collisions.\n */\nfunction arrowMarkerDefsForColor(color: string): string {\n return arrowMarkerPair(escapeAttr(color), `-${markerSuffix(color)}`)\n}\n\n/**\n * Build the forward (marker-end) + reverse (marker-start) arrow-head marker pair.\n *\n * Both heads share one polygon. The reverse head differs only by\n * orient=\"auto-start-reverse\", which flips it 180° to point back out of\n * the start node — so the polygon must NOT be pre-reversed. Reversing both is\n * a double reversal: the head points into the line and vanishes in\n * librsvg/Inkscape/browsers.\n */\nfunction arrowMarkerPair(color: string, idSuffix: string): string {\n const w = ARROW_HEAD.width\n const h = ARROW_HEAD.height\n // Pull arrowhead back slightly (refX = w - 1) to prevent clipping at node boundaries.\n const refX = w - 1\n // Both fill and a thin stroke for better definition at small sizes.\n const style = `fill=\"${color}\" stroke=\"${color}\" stroke-width=\"0.75\" stroke-linejoin=\"round\"`\n const polygon = `<polygon points=\"0 0, ${w} ${h / 2}, 0 ${h}\" ${style} />`\n const marker = (id: string, orient: string) =>\n ` <marker id=\"${id}\" markerWidth=\"${w}\" markerHeight=\"${h}\" refX=\"${refX}\" refY=\"${h / 2}\" orient=\"${orient}\">` +\n `\\n ${polygon}` +\n `\\n </marker>`\n return (\n marker(`arrowhead${idSuffix}`, 'auto') +\n '\\n' +\n marker(`arrowhead-start${idSuffix}`, 'auto-start-reverse')\n )\n}\n\n/** Sanitize a color value into a collision-free SVG ID suffix.\n * Non-alphanumeric chars are hex-encoded so distinct inputs never collapse\n * (e.g. \"var(--line-1)\" → \"var28--line2d129\", \"var(--line1)\" → \"var28--line129\"). */\nfunction markerSuffix(color: string): string {\n return color.replace(/[^a-zA-Z0-9]/g, (ch) => ch.charCodeAt(0).toString(16))\n}\n\n// ============================================================================\n// Group rendering (subgraph backgrounds)\n// ============================================================================\n\nfunction renderGroup(\n group: PositionedGroup,\n font: string,\n fontSizes: FontSizes,\n): string {\n const headerHeight = fontSizes.groupHeader + 16\n const parts: string[] = []\n\n // Opening <g> with semantic attributes for subgraph identification\n // data-id: original Mermaid subgraph ID\n // data-label: display label (may differ from ID)\n parts.push(\n `<g class=\"subgraph\" data-id=\"${escapeAttr(group.id)}\" data-label=\"${escapeAttr(group.label)}\">`,\n )\n\n // Outer rectangle\n parts.push(\n ` <rect x=\"${group.x}\" y=\"${group.y}\" width=\"${group.width}\" height=\"${group.height}\" ` +\n `rx=\"0\" ry=\"0\" fill=\"var(--_group-fill)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n\n // Header band\n parts.push(\n ` <rect x=\"${group.x}\" y=\"${group.y}\" width=\"${group.width}\" height=\"${headerHeight}\" ` +\n `rx=\"0\" ry=\"0\" fill=\"var(--_group-hdr)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n\n // Header label (supports multi-line via <br> tags)\n parts.push(\n ' ' +\n renderMultilineText(\n group.label,\n group.x + 12,\n group.y + headerHeight / 2,\n fontSizes.groupHeader,\n `font-size=\"${fontSizes.groupHeader}\" font-weight=\"${FONT_WEIGHTS.groupHeader}\" fill=\"var(--_text-sec)\"`,\n ),\n )\n\n // Render nested groups recursively (inside this group)\n for (const child of group.children) {\n parts.push(renderGroup(child, font, fontSizes))\n }\n\n parts.push('</g>')\n\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Edge rendering\n// ============================================================================\n\nfunction renderEdge(\n edge: PositionedEdge,\n curve: CurveStyle,\n animationEnabled: boolean = true,\n): string {\n if (edge.points.length < 2) return ''\n\n // `interactivity: 'none'` strips motion: the edge still renders (id,\n // data-* attributes, arrowheads) but never gets the animated class or dash.\n const animate = animationEnabled && (edge.animate ?? false)\n\n /*\n * A curved edge must be a <path>; only a path can express the\n * interpolations Mermaid's `flowchart.curve` selects.\n *\n * The default (`linear`) deliberately keeps emitting <polyline>. A path of\n * straight `L` segments would be geometrically identical, but changing the\n * element for every diagram would break any consumer selecting\n * `polyline.edge` — for no benefit to a diagram that asked for no curve.\n * So the element changes only when the author opts into a curve.\n */\n const curved = curve !== 'linear'\n\n /*\n * An invisible link (`A ~~~ B`) still occupies its layout slot — that is\n * the whole point of the syntax — so the element is emitted with its data\n * attributes intact and only its paint suppressed. Omitting the element\n * entirely would lose it from DOM inspection and from `data-style` queries.\n */\n const invisible = edge.style === 'invisible'\n\n const dashArray = edge.style === 'dotted' ? ' stroke-dasharray=\"4 4\"' : ''\n const baseStrokeWidth =\n edge.style === 'thick'\n ? STROKE_WIDTHS.connector * 2\n : STROKE_WIDTHS.connector\n const strokeColor = invisible\n ? 'none'\n : escapeAttr(edge.inlineStyle?.stroke ?? 'var(--_line)')\n const strokeWidth = escapeAttr(\n edge.inlineStyle?.['stroke-width'] ?? String(baseStrokeWidth),\n )\n\n // Build marker attributes based on arrow direction flags\n // Use color-specific markers when edge has a custom stroke from linkStyle\n const suffix = edge.inlineStyle?.stroke\n ? `-${markerSuffix(edge.inlineStyle.stroke)}`\n : ''\n let markers = ''\n if (!invisible) {\n if (edge.hasArrowEnd) markers += ` marker-end=\"url(#arrowhead${suffix})\"`\n if (edge.hasArrowStart)\n markers += ` marker-start=\"url(#arrowhead-start${suffix})\"`\n }\n\n // Semantic data attributes for edge identification and inspection:\n // - class=\"edge\": CSS targeting and type identification\n // - data-from/data-to: source and target node IDs\n // - data-style: edge style (solid, dotted, thick)\n // - data-arrow-start/end: arrow presence flags\n // - data-label: edge label if present (for quick lookup without traversing DOM)\n const dataAttrs = [\n // An animated edge (`e1@{ animate: true }`, with animation enabled)\n // carries an extra class; its keyframes live in the shared style block.\n `class=\"edge${animate ? ' edge-animated' : ''}\"`,\n `data-from=\"${escapeAttr(edge.source)}\"`,\n `data-to=\"${escapeAttr(edge.target)}\"`,\n `data-style=\"${edge.style}\"`,\n `data-arrow-start=\"${edge.hasArrowStart}\"`,\n `data-arrow-end=\"${edge.hasArrowEnd}\"`,\n ]\n if (edge.label) {\n dataAttrs.push(`data-label=\"${escapeAttr(edge.label)}\"`)\n }\n if (edge.id) {\n // Mermaid edge id (`A e1@--> B`), used to target the edge from CSS and\n // to attach the animation below.\n dataAttrs.push(`data-id=\"${escapeAttr(edge.id)}\"`)\n }\n\n // Marching ants need a dash pattern to march; supply one only when the\n // edge's own style hasn't already set stroke-dasharray.\n const animatedDash = animate && !dashArray ? ' stroke-dasharray=\"8 6\"' : ''\n\n const geometry = curved\n ? `d=\"${pointsToPath(edge.points, curve)}\"`\n : `points=\"${pointsToPolylinePath(edge.points)}\"`\n\n return (\n `<${curved ? 'path' : 'polyline'} ${dataAttrs.join(' ')} ${geometry} ` +\n `fill=\"none\" stroke=\"${strokeColor}\" ` +\n `stroke-width=\"${strokeWidth}\"${dashArray}${animatedDash}${markers} />`\n )\n}\n\n/** Convert points to SVG polyline points attribute: \"x1,y1 x2,y2 ...\" */\nfunction pointsToPolylinePath(points: Point[]): string {\n return points.map((p) => `${p.x},${p.y}`).join(' ')\n}\n\n// `_font` isn't read here but is kept to match the `(entity, font)` signature\n// threaded through the rest of the render* functions in this file.\nfunction renderEdgeLabel(\n edge: PositionedEdge,\n _font: string,\n fontSizes: FontSizes,\n): string {\n // Only called when edge.label is set (see call site), but narrow it here\n // too rather than trusting that invariant across the function boundary.\n if (!edge.label) return ''\n const label = edge.label\n\n // Use layout-computed label position when available (layout-aware, avoids collisions).\n // Fall back to geometric midpoint of the edge polyline.\n const mid = edge.labelPosition ?? edgeMidpoint(edge.points)\n const padding = 8\n\n // Measure text (works for both single and multi-line)\n const metrics = measureMultilineText(\n label,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n\n // Wrap in <g class=\"edge-label\"> with reference to the edge it belongs to\n const content = renderMultilineTextWithBackground(\n label,\n mid.x,\n mid.y,\n metrics.width,\n metrics.height,\n fontSizes.edgeLabel,\n padding,\n // Use --_text-sec for better contrast (was --_text-muted)\n `text-anchor=\"middle\" font-size=\"${fontSizes.edgeLabel}\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-sec)\"`,\n // Increased stroke width from 0.5 to 1 for better label separation from edges\n `rx=\"2\" ry=\"2\" fill=\"var(--bg)\" stroke=\"var(--_inner-stroke)\" stroke-width=\"1\"`,\n )\n\n // Semantic wrapper: links label to its edge via data-from/data-to\n return (\n `<g class=\"edge-label\" data-from=\"${escapeAttr(edge.source)}\" data-to=\"${escapeAttr(edge.target)}\" data-label=\"${escapeAttr(label)}\">\\n` +\n ` ${content.replace(/\\n/g, '\\n ')}\\n` +\n `</g>`\n )\n}\n\n/**\n * Get the point at `index`, throwing instead of silently producing\n * `undefined` (and downstream `NaN`s). The loops in `edgeMidpoint` only ever\n * pass indices derived from `points.length`, so this should never actually\n * throw — it exists because `noUncheckedIndexedAccess` can't prove that from\n * the loop bounds alone, and a loud failure here is far easier to diagnose\n * than silently corrupted coordinates.\n */\nfunction requirePoint(points: Point[], index: number): Point {\n const point = points[index]\n if (!point) {\n // Unreachable — both call sites in edgeMidpoint only ever pass indices\n // in [0, points.length - 1].\n /* v8 ignore next */\n throw new Error(`edgeMidpoint: missing point at index ${index}`)\n }\n return point\n}\n\n/** Get the midpoint of a polyline (by walking segments) */\nfunction edgeMidpoint(points: Point[]): Point {\n if (points.length === 0) return { x: 0, y: 0 }\n if (points.length === 1) return points[0]!\n\n // Calculate total length\n let totalLength = 0\n for (let i = 1; i < points.length; i++) {\n totalLength += dist(requirePoint(points, i - 1), requirePoint(points, i))\n }\n\n // Walk to the halfway point\n let remaining = totalLength / 2\n for (let i = 1; i < points.length; i++) {\n const prev = requirePoint(points, i - 1)\n const curr = requirePoint(points, i)\n const segLen = dist(prev, curr)\n if (remaining <= segLen) {\n const t = remaining / segLen\n return {\n x: prev.x + t * (curr.x - prev.x),\n y: prev.y + t * (curr.y - prev.y),\n }\n }\n remaining -= segLen\n }\n\n return points[points.length - 1]!\n}\n\nfunction dist(a: Point, b: Point): number {\n return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2)\n}\n\n// ============================================================================\n// Node rendering\n// ============================================================================\n\n/**\n * Render a complete node: shape + label wrapped in a semantic <g> element.\n *\n * The group includes data attributes for:\n * - data-id: original Mermaid node ID (for edge matching)\n * - data-label: display label text\n * - data-shape: shape type (rectangle, diamond, circle, etc.)\n *\n * @param linksEnabled - Whether a `click`-based `<a href>` link and `<title>`\n * tooltip render (from `options.interactivity !==\n * 'none'`). Default true.\n */\nfunction renderNode(\n node: PositionedNode,\n font: string,\n fontSizes: FontSizes,\n linksEnabled: boolean = true,\n): string {\n const shape = renderNodeShape(node)\n const label = renderNodeLabel(node, font, fontSizes)\n\n // Combine shape and label inside a semantic group\n // This enables reliable node identification without heuristics\n const parts: string[] = []\n const safeClassName = sanitizeClassName(node.className)\n const classAttr = safeClassName ? `node ${safeClassName}` : 'node'\n\n const interaction = node.interaction\n const groupAttrs = [\n `class=\"${classAttr}\"`,\n `data-id=\"${escapeAttr(node.id)}\"`,\n `data-label=\"${escapeAttr(node.label)}\"`,\n `data-shape=\"${node.shape}\"`,\n ]\n /*\n * `click A call fn()` is deliberately absent from the markup. This renderer\n * produces a static SVG string and executes nothing a diagram supplies —\n * running diagram-authored script would make every rendered diagram an\n * execution vector. The binding is exposed as data instead, on the\n * `interactions` map `parseMermaid()` returns, and the `data-id` attribute\n * above is the hook a host binds it to. The inert `data-click-callback`\n * attribute once emitted here was removed in #216 — see\n * docs/decisions/no-script-interactivity.md.\n */\n parts.push(`<g ${groupAttrs.join(' ')}>`)\n\n // An href becomes a real SVG link, which needs no script to work.\n // `interactivity: 'none'` strips it — a link is meaningless in\n // print/rasterized output, the target that level is meant for.\n const href = linksEnabled ? safeHref(interaction?.href) : undefined\n const indent = href ? ' ' : ' '\n if (href) {\n const targetAttr = interaction?.target\n ? ` target=\"${escapeAttr(interaction.target)}\"`\n : ''\n parts.push(` <a href=\"${escapeAttr(href)}\"${targetAttr}>`)\n }\n\n if (linksEnabled && interaction?.tooltip) {\n // <title> is SVG's native tooltip — no script, no CSS. Gated by\n // linksEnabled alongside href above: both come from the same `click`\n // statement, and 'none' strips both.\n parts.push(`${indent}<title>${escapeXml(interaction.tooltip)}</title>`)\n }\n\n parts.push(`${indent}${shape.replace(/\\n/g, `\\n${indent}`)}`)\n if (label) {\n parts.push(`${indent}${label.replace(/\\n/g, `\\n${indent}`)}`)\n }\n\n if (href) parts.push(' </a>')\n parts.push('</g>')\n\n return parts.join('\\n')\n}\n\nfunction renderNodeShape(node: PositionedNode): string {\n const { x, y, width, height, shape, inlineStyle } = node\n\n // Resolve fill and stroke — inline styles (from mermaid `style` directives)\n // override the CSS variable defaults. When no inline style is present, the\n // CSS variable handles theming automatically via color-mix() derivation.\n const fill = escapeAttr(inlineStyle?.fill ?? 'var(--_node-fill)')\n const stroke = escapeAttr(inlineStyle?.stroke ?? 'var(--_node-stroke)')\n const sw = escapeAttr(\n inlineStyle?.['stroke-width'] ?? String(STROKE_WIDTHS.innerBox),\n )\n\n switch (shape) {\n case 'diamond':\n return renderDiamond(x, y, width, height, fill, stroke, sw)\n case 'rounded':\n return renderRoundedRect(x, y, width, height, fill, stroke, sw)\n case 'stadium':\n return renderStadium(x, y, width, height, fill, stroke, sw)\n case 'circle':\n return renderCircle(x, y, width, height, fill, stroke, sw)\n case 'subroutine':\n return renderSubroutine(x, y, width, height, fill, stroke, sw)\n case 'doublecircle':\n return renderDoubleCircle(x, y, width, height, fill, stroke, sw)\n case 'hexagon':\n return renderHexagon(x, y, width, height, fill, stroke, sw)\n case 'cylinder':\n return renderCylinder(x, y, width, height, fill, stroke, sw)\n case 'asymmetric':\n return renderAsymmetric(x, y, width, height, fill, stroke, sw)\n case 'trapezoid':\n return renderTrapezoid(x, y, width, height, fill, stroke, sw)\n case 'trapezoid-alt':\n return renderTrapezoidAlt(x, y, width, height, fill, stroke, sw)\n case 'parallelogram':\n return renderParallelogram(x, y, width, height, fill, stroke, sw)\n case 'parallelogram-alt':\n return renderParallelogramAlt(x, y, width, height, fill, stroke, sw)\n case 'state-start':\n return renderStateStart(x, y, width, height)\n case 'state-end':\n return renderStateEnd(x, y, width, height)\n\n // --- Expanded-syntax shapes (`A@{ shape: ... }`) ---\n case 'document':\n return renderDocument(x, y, width, height, fill, stroke, sw)\n case 'stacked-document':\n return renderStacked(x, y, width, height, fill, stroke, sw, true)\n case 'stacked-process':\n return renderStacked(x, y, width, height, fill, stroke, sw, false)\n case 'card':\n return renderCard(x, y, width, height, fill, stroke, sw)\n case 'lined-process':\n return renderLinedProcess(x, y, width, height, fill, stroke, sw)\n case 'divided-process':\n return renderDividedProcess(x, y, width, height, fill, stroke, sw)\n case 'window-pane':\n return renderWindowPane(x, y, width, height, fill, stroke, sw)\n case 'triangle':\n return renderTriangle(x, y, width, height, fill, stroke, sw, false)\n case 'flipped-triangle':\n return renderTriangle(x, y, width, height, fill, stroke, sw, true)\n case 'filled-circle':\n return renderFilledCircle(x, y, width, height, stroke)\n case 'crossed-circle':\n return renderCrossedCircle(x, y, width, height, fill, stroke, sw)\n case 'fork-join':\n return renderForkJoin(x, y, width, height, stroke)\n case 'notched-pentagon':\n return renderNotchedPentagon(x, y, width, height, fill, stroke, sw)\n case 'sloped-rectangle':\n return renderSlopedRectangle(x, y, width, height, fill, stroke, sw)\n case 'flag':\n return renderFlag(x, y, width, height, fill, stroke, sw)\n case 'bow-tie-rectangle':\n return renderBowTie(x, y, width, height, fill, stroke, sw)\n case 'half-rounded-rectangle':\n return renderHalfRounded(x, y, width, height, fill, stroke, sw)\n case 'brace':\n return renderBraces(x, y, width, height, fill, stroke, sw, 'left')\n case 'brace-right':\n return renderBraces(x, y, width, height, fill, stroke, sw, 'right')\n case 'braces':\n return renderBraces(x, y, width, height, fill, stroke, sw, 'both')\n case 'bolt':\n return renderBolt(x, y, width, height, fill, stroke, sw)\n case 'text':\n case 'anchor':\n // No outline — the label (rendered separately) is the whole node.\n return ''\n\n case 'rectangle':\n default:\n return renderRect(x, y, width, height, fill, stroke, sw)\n }\n}\n\n// --- Basic shapes ---\n\nfunction renderRect(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n return (\n `<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ` +\n `rx=\"0\" ry=\"0\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\nfunction renderRoundedRect(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n return (\n `<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ` +\n `rx=\"6\" ry=\"6\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\nfunction renderStadium(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const r = h / 2\n return (\n `<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ` +\n `rx=\"${r}\" ry=\"${r}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\nfunction renderCircle(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const r = Math.min(w, h) / 2\n return (\n `<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${r}\" ` +\n `fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\nfunction renderDiamond(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const hw = w / 2\n const hh = h / 2\n const points = [\n `${cx},${cy - hh}`, // top\n `${cx + hw},${cy}`, // right\n `${cx},${cy + hh}`, // bottom\n `${cx - hw},${cy}`, // left\n ].join(' ')\n\n return `<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n// --- Batch 1 shapes ---\n\n/** Subroutine: rectangle with double vertical borders on left and right */\nfunction renderSubroutine(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = 8 // distance from edge to inner vertical line\n return (\n `<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ` +\n `rx=\"0\" ry=\"0\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n `\\n<line x1=\"${x + inset}\" y1=\"${y}\" x2=\"${x + inset}\" y2=\"${y + h}\" ` +\n `stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n `\\n<line x1=\"${x + w - inset}\" y1=\"${y}\" x2=\"${x + w - inset}\" y2=\"${y + h}\" ` +\n `stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Double circle: two concentric circles with a gap between them */\nfunction renderDoubleCircle(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const outerR = Math.min(w, h) / 2\n const innerR = outerR - 5 // 5px gap between rings\n return (\n `<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${outerR}\" ` +\n `fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n `\\n<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${innerR}\" ` +\n `fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Hexagon: 6-point polygon with flat top/bottom and angled sides */\nfunction renderHexagon(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = h / 4 // horizontal inset for the angled sides\n const points = [\n `${x + inset},${y}`, // top-left\n `${x + w - inset},${y}`, // top-right\n `${x + w},${y + h / 2}`, // mid-right\n `${x + w - inset},${y + h}`, // bottom-right\n `${x + inset},${y + h}`, // bottom-left\n `${x},${y + h / 2}`, // mid-left\n ].join(' ')\n\n return `<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n// --- Batch 2 shapes ---\n\n/** Cylinder / database: top ellipse cap + body rect + bottom ellipse */\nfunction renderCylinder(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const ry = 7 // ellipse vertical radius for the cap\n const cx = x + w / 2\n const bodyTop = y + ry\n const bodyH = h - 2 * ry\n\n return (\n // Body rectangle (no top border — covered by top ellipse)\n `<rect x=\"${x}\" y=\"${bodyTop}\" width=\"${w}\" height=\"${bodyH}\" ` +\n `fill=\"${fill}\" stroke=\"none\" />` +\n // Left and right body borders\n `\\n<line x1=\"${x}\" y1=\"${bodyTop}\" x2=\"${x}\" y2=\"${bodyTop + bodyH}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n `\\n<line x1=\"${x + w}\" y1=\"${bodyTop}\" x2=\"${x + w}\" y2=\"${bodyTop + bodyH}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n // Bottom ellipse (half visible)\n `\\n<ellipse cx=\"${cx}\" cy=\"${y + h - ry}\" rx=\"${w / 2}\" ry=\"${ry}\" ` +\n `fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n // Top ellipse (full, on top)\n `\\n<ellipse cx=\"${cx}\" cy=\"${bodyTop}\" rx=\"${w / 2}\" ry=\"${ry}\" ` +\n `fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Asymmetric / flag: rectangle with a pointed left edge */\nfunction renderAsymmetric(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const indent = 12 // how far the point indents\n const points = [\n `${x + indent},${y}`, // top-left (indented)\n `${x + w},${y}`, // top-right\n `${x + w},${y + h}`, // bottom-right\n `${x + indent},${y + h}`, // bottom-left (indented)\n `${x},${y + h / 2}`, // left point\n ].join(' ')\n\n return `<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n/** Trapezoid [/text\\]: wider bottom, narrower top */\nfunction renderTrapezoid(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = w * 0.15 // top edge is narrower by this amount on each side\n const points = [\n `${x + inset},${y}`, // top-left (indented)\n `${x + w - inset},${y}`, // top-right (indented)\n `${x + w},${y + h}`, // bottom-right (full width)\n `${x},${y + h}`, // bottom-left (full width)\n ].join(' ')\n\n return `<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n/** Trapezoid-alt [\\text/]: wider top, narrower bottom */\nfunction renderTrapezoidAlt(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = w * 0.15 // bottom edge is narrower\n const points = [\n `${x},${y}`, // top-left (full width)\n `${x + w},${y}`, // top-right (full width)\n `${x + w - inset},${y + h}`, // bottom-right (indented)\n `${x + inset},${y + h}`, // bottom-left (indented)\n ].join(' ')\n\n return `<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n/**\n * Parallelogram [/text/]: leans right.\n *\n * Unlike the trapezoids, both sloped sides run the same direction — the top\n * edge shifts right by `inset` and the bottom edge shifts left by the same\n * amount, so opposite sides stay parallel.\n */\nfunction renderParallelogram(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = w * 0.15\n const points = [\n `${x + inset},${y}`, // top-left (shifted right)\n `${x + w},${y}`, // top-right\n `${x + w - inset},${y + h}`, // bottom-right (shifted left)\n `${x},${y + h}`, // bottom-left\n ].join(' ')\n\n return `<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n/** Parallelogram-alt [\\text\\]: leans left — the mirror of renderParallelogram. */\nfunction renderParallelogramAlt(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = w * 0.15\n const points = [\n `${x},${y}`, // top-left\n `${x + w - inset},${y}`, // top-right (shifted left)\n `${x + w},${y + h}`, // bottom-right\n `${x + inset},${y + h}`, // bottom-left (shifted right)\n ].join(' ')\n\n return `<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n// --- Expanded-syntax shapes (`A@{ shape: ... }`) ---\n//\n// Each of these is reachable only through the expanded metadata syntax; the\n// classic bracket forms have no spelling for them. See\n// packages/mermaid-parser/src/expanded-shapes.ts for the semantic-name →\n// geometry alias table.\n\n/** Shared attribute string for a filled, stroked path. */\nfunction shapeAttrs(fill: string, stroke: string, sw: string): string {\n return `fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\"`\n}\n\n/**\n * Document: rectangle with a wavy bottom edge.\n *\n * The wave is two cubic segments — down then up — so the edge returns to its\n * starting height at the right corner and the shape tiles cleanly when\n * stacked (see renderStacked).\n */\nfunction renderDocument(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const waveH = Math.min(10, h * 0.16)\n const base = y + h - waveH\n // Both ends land on `base`, so the left and right sides are the same\n // height. Ending the curve anywhere else makes every document node\n // visibly lopsided.\n const d =\n `M ${x} ${y} L ${x + w} ${y} L ${x + w} ${base} ` +\n `C ${x + w * 0.75} ${base + waveH * 1.8} ${x + w * 0.25} ${base - waveH * 0.8} ${x} ${base} Z`\n return `<path d=\"${d}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Stacked document / process: offset copies behind the front shape. */\nfunction renderStacked(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n isDocument: boolean,\n): string {\n const offset = 5\n const frontW = w - offset * 2\n const frontH = h - offset * 2\n const parts: string[] = []\n\n // Two offset copies behind, back to front.\n for (let i = 2; i >= 1; i--) {\n parts.push(\n `<rect x=\"${x + offset * i}\" y=\"${y + offset * (2 - i)}\" width=\"${frontW}\" height=\"${frontH}\" ` +\n `${shapeAttrs(fill, stroke, sw)} />`,\n )\n }\n\n parts.push(\n isDocument\n ? renderDocument(x, y + offset * 2, frontW, frontH, fill, stroke, sw)\n : `<rect x=\"${x}\" y=\"${y + offset * 2}\" width=\"${frontW}\" height=\"${frontH}\" ${shapeAttrs(fill, stroke, sw)} />`,\n )\n\n return parts.join('\\n')\n}\n\n/** Card / notched rectangle: top-left corner clipped. */\nfunction renderCard(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const notch = Math.min(14, w * 0.12, h * 0.3)\n const points = [\n `${x + notch},${y}`,\n `${x + w},${y}`,\n `${x + w},${y + h}`,\n `${x},${y + h}`,\n `${x},${y + notch}`,\n ].join(' ')\n return `<polygon points=\"${points}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Lined process: rectangle with a vertical rule inset from the left edge. */\nfunction renderLinedProcess(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = Math.min(12, w * 0.12)\n return (\n `<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ${shapeAttrs(fill, stroke, sw)} />\\n` +\n `<line x1=\"${x + inset}\" y1=\"${y}\" x2=\"${x + inset}\" y2=\"${y + h}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Divided process: rectangle split by a horizontal rule near the top. */\nfunction renderDividedProcess(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const split = y + Math.min(16, h * 0.3)\n return (\n `<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ${shapeAttrs(fill, stroke, sw)} />\\n` +\n `<line x1=\"${x}\" y1=\"${split}\" x2=\"${x + w}\" y2=\"${split}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Window pane / internal storage: rectangle quartered by a cross. */\nfunction renderWindowPane(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const vx = x + Math.min(16, w * 0.16)\n const hy = y + Math.min(14, h * 0.28)\n return (\n `<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ${shapeAttrs(fill, stroke, sw)} />\\n` +\n `<line x1=\"${vx}\" y1=\"${y}\" x2=\"${vx}\" y2=\"${y + h}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />\\n` +\n `<line x1=\"${x}\" y1=\"${hy}\" x2=\"${x + w}\" y2=\"${hy}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Triangle, apex up (extract) or apex down (manual file). */\nfunction renderTriangle(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n flipped: boolean,\n): string {\n const points = flipped\n ? [`${x},${y}`, `${x + w},${y}`, `${x + w / 2},${y + h}`]\n : [`${x + w / 2},${y}`, `${x + w},${y + h}`, `${x},${y + h}`]\n return `<polygon points=\"${points.join(' ')}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Filled circle (junction): a solid dot, so it takes the stroke color as fill. */\nfunction renderFilledCircle(\n x: number,\n y: number,\n w: number,\n h: number,\n stroke: string,\n): string {\n const r = Math.min(w, h) / 2 - 2\n return `<circle cx=\"${x + w / 2}\" cy=\"${y + h / 2}\" r=\"${r}\" fill=\"${stroke}\" stroke=\"${stroke}\" />`\n}\n\n/** Crossed circle (summary): circle with an X through it. */\nfunction renderCrossedCircle(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const r = Math.min(w, h) / 2 - 2\n // Cross arms meet the circumference at 45°, so offset by r/√2.\n const d = r / Math.SQRT2\n return (\n `<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${r}\" ${shapeAttrs(fill, stroke, sw)} />\\n` +\n `<line x1=\"${cx - d}\" y1=\"${cy - d}\" x2=\"${cx + d}\" y2=\"${cy + d}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />\\n` +\n `<line x1=\"${cx + d}\" y1=\"${cy - d}\" x2=\"${cx - d}\" y2=\"${cy + d}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Fork/join: a solid bar, drawn in the stroke color. */\nfunction renderForkJoin(\n x: number,\n y: number,\n w: number,\n h: number,\n stroke: string,\n): string {\n const barH = Math.min(8, h)\n return `<rect x=\"${x}\" y=\"${y + (h - barH) / 2}\" width=\"${w}\" height=\"${barH}\" fill=\"${stroke}\" stroke=\"${stroke}\" />`\n}\n\n/** Notched pentagon (loop limit): both top corners clipped. */\nfunction renderNotchedPentagon(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const notch = Math.min(14, w * 0.12, h * 0.3)\n const points = [\n `${x + notch},${y}`,\n `${x + w - notch},${y}`,\n `${x + w},${y + notch}`,\n `${x + w},${y + h}`,\n `${x},${y + h}`,\n `${x},${y + notch}`,\n ].join(' ')\n return `<polygon points=\"${points}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Sloped rectangle (manual input): top edge slopes up to the right. */\nfunction renderSlopedRectangle(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const slope = Math.min(12, h * 0.3)\n const points = [\n `${x},${y + slope}`,\n `${x + w},${y}`,\n `${x + w},${y + h}`,\n `${x},${y + h}`,\n ].join(' ')\n return `<polygon points=\"${points}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Flag / paper tape: wavy top and bottom edges. */\nfunction renderFlag(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const waveH = Math.min(8, h * 0.14)\n const d =\n `M ${x} ${y + waveH} ` +\n `C ${x + w * 0.25} ${y - waveH} ${x + w * 0.75} ${y + waveH * 2} ${x + w} ${y + waveH} ` +\n `L ${x + w} ${y + h - waveH} ` +\n `C ${x + w * 0.75} ${y + h + waveH} ${x + w * 0.25} ${y + h - waveH * 2} ${x} ${y + h - waveH} Z`\n return `<path d=\"${d}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Bow-tie rectangle (stored data): left and right edges curve inward. */\nfunction renderBowTie(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const bow = Math.min(14, w * 0.12)\n const d =\n `M ${x + bow} ${y} L ${x + w} ${y} ` +\n `Q ${x + w - bow * 1.4} ${y + h / 2} ${x + w} ${y + h} ` +\n `L ${x + bow} ${y + h} ` +\n `Q ${x + bow * 1.4} ${y + h / 2} ${x + bow} ${y} Z`\n return `<path d=\"${d}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Delay / half-rounded rectangle: the right end is a semicircle. */\nfunction renderHalfRounded(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const r = h / 2\n const straight = Math.max(0, w - r)\n const d =\n `M ${x} ${y} L ${x + straight} ${y} ` +\n `A ${r} ${r} 0 0 1 ${x + straight} ${y + h} ` +\n `L ${x} ${y + h} Z`\n return `<path d=\"${d}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/**\n * Brace shapes: a rectangle body flanked by curly braces.\n *\n * Mermaid's `brace`/`comment` is a left brace, `brace-r` a right brace, and\n * `braces` both. The body itself is unfilled so the braces read as annotation\n * marks rather than as a container.\n */\nfunction renderBraces(\n x: number,\n y: number,\n w: number,\n h: number,\n _fill: string,\n stroke: string,\n sw: string,\n side: 'left' | 'right' | 'both',\n): string {\n const armW = Math.min(10, w * 0.12)\n // Unfilled: a filled body reads as a container, which is the opposite of\n // what a brace annotation means. The rect stays only to reserve the\n // label area; `_fill` is deliberately unused.\n const parts = [\n `<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" fill=\"none\" stroke=\"none\" />`,\n ]\n\n const leftBrace =\n `M ${x + armW} ${y} Q ${x} ${y} ${x} ${y + h * 0.25} ` +\n `Q ${x} ${y + h / 2} ${x - armW * 0.4} ${y + h / 2} ` +\n `Q ${x} ${y + h / 2} ${x} ${y + h * 0.75} ` +\n `Q ${x} ${y + h} ${x + armW} ${y + h}`\n const rightBrace =\n `M ${x + w - armW} ${y} Q ${x + w} ${y} ${x + w} ${y + h * 0.25} ` +\n `Q ${x + w} ${y + h / 2} ${x + w + armW * 0.4} ${y + h / 2} ` +\n `Q ${x + w} ${y + h / 2} ${x + w} ${y + h * 0.75} ` +\n `Q ${x + w} ${y + h} ${x + w - armW} ${y + h}`\n\n if (side === 'left' || side === 'both') {\n parts.push(\n `<path d=\"${leftBrace}\" fill=\"none\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`,\n )\n }\n if (side === 'right' || side === 'both') {\n parts.push(\n `<path d=\"${rightBrace}\" fill=\"none\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`,\n )\n }\n\n return parts.join('\\n')\n}\n\n/** Lightning bolt (communication link). */\nfunction renderBolt(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const points = [\n `${x + w * 0.42},${y}`,\n `${x + w},${y}`,\n `${x + w * 0.62},${y + h * 0.42}`,\n `${x + w},${y + h * 0.42}`,\n `${x + w * 0.3},${y + h}`,\n `${x + w * 0.5},${y + h * 0.55}`,\n `${x},${y + h * 0.55}`,\n ].join(' ')\n return `<polygon points=\"${points}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n// --- Batch 3: State diagram pseudostates ---\n\n/** State start: small filled circle using primary text color */\nfunction renderStateStart(x: number, y: number, w: number, h: number): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const r = Math.min(w, h) / 2 - 2\n return `<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${r}\" fill=\"var(--_text)\" stroke=\"none\" />`\n}\n\n/** State end: bullseye — outer ring + inner filled circle using primary text color */\nfunction renderStateEnd(x: number, y: number, w: number, h: number): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const outerR = Math.min(w, h) / 2 - 2\n const innerR = outerR - 4\n return (\n `<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${outerR}\" ` +\n `fill=\"none\" stroke=\"var(--_text)\" stroke-width=\"${STROKE_WIDTHS.innerBox * 2}\" />` +\n `\\n<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${innerR}\" fill=\"var(--_text)\" stroke=\"none\" />`\n )\n}\n\n// ============================================================================\n// Node label rendering\n// ============================================================================\n\n// `_font` isn't read here but is kept to match the `(entity, font)` signature\n// threaded through the rest of the render* functions in this file.\nfunction renderNodeLabel(\n node: PositionedNode,\n _font: string,\n fontSizes: FontSizes,\n): string {\n // State pseudostates have no label\n if (node.shape === 'state-start' || node.shape === 'state-end') {\n if (!node.label) return ''\n }\n\n const cx = node.x + node.width / 2\n const cy = node.y + node.height / 2\n\n // Resolve text color — inline styles can override the CSS variable default.\n // When there's no explicit `color` but there IS a concrete, resolvable\n // `fill` (e.g. from classDef/style), compute a readable black/white text\n // color from the fill's luminance instead of defaulting to the ambient\n // theme foreground, which can be unreadable against a custom fill (e.g.\n // white theme text on a light pastel fill in dark mode). See issue #55.\n const textColor = escapeAttr(\n node.inlineStyle?.color ??\n getReadableTextColor(node.inlineStyle?.fill, 'var(--_text)'),\n )\n\n let attrs = `text-anchor=\"middle\" font-size=\"${fontSizes.nodeLabel}\" font-weight=\"${FONT_WEIGHTS.nodeLabel}\" fill=\"${textColor}\"`\n\n // Per-node font-family override (from `style A font-family:...` or\n // classDef/class). Emitted as an inline `style` attribute rather than a\n // `font-family` presentation attribute: the global font is applied via a\n // `text { font-family: ... }` rule in the embedded <style> block (see\n // theme.ts buildStyleBlock), and a presentation attribute always loses to\n // any stylesheet rule regardless of selector specificity. An inline `style`\n // attribute has the highest priority in the cascade, so it reliably\n // overrides the global rule for just this node while every other node\n // keeps falling back to the global font stack. See issue #57.\n const fontFamily = node.inlineStyle?.['font-family']\n if (fontFamily) {\n attrs += ` style=\"font-family: ${escapeAttr(fontFamily)};\"`\n }\n\n return renderMultilineText(node.label, cx, cy, fontSizes.nodeLabel, attrs)\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Escape a string for embedding as a *multiline-safe* XML attribute value —\n * `escapeAttr()` plus tab/LF/CR as numeric character references.\n *\n * A strict XML parser applies attribute-value normalization on the way in:\n * any literal tab, LF, or CR in the value is collapsed to a single space.\n * (This is why a standalone .svg file opened directly, or any output run\n * through `DOMParser` with an XML/`image/svg+xml` mimetype, would silently\n * flatten a multiline `data-src` back to one line.) Character references\n * are exempt from that normalization — `&#10;` round-trips as an actual\n * newline — so diagram source, which is virtually always multiline, needs\n * this rather than `escapeAttr()` alone to survive the trip intact.\n */\nfunction escapeMultilineAttr(value: string): string {\n return escapeAttr(value)\n .replace(/\\r/g, '&#13;')\n .replace(/\\n/g, '&#10;')\n .replace(/\\t/g, '&#9;')\n}\n\n/**\n * Splice a `data-src` attribute (the original diagram source, escaped) onto\n * an already-built root `<svg ...>` opening tag, e.g. from\n * `embedSource: true`. Applied to the string `svgOpenTag()` returns rather\n * than the diagram source's own markup, so it always lands on the root\n * element regardless of diagram type. No-op when `source` is undefined.\n */\nexport function withDataSrc(\n svgTag: string,\n source: string | undefined,\n): string {\n if (source === undefined) return svgTag\n return svgTag.replace(\n '<svg ',\n `<svg data-src=\"${escapeMultilineAttr(source)}\" `,\n )\n}\n","// ============================================================================\n// `resolveColors` — substitute concrete sRGB values for the CSS `var(--…)`\n// and `color-mix(…)` expressions the SVG renderer emits.\n//\n// Browsers evaluate both natively, which is what makes the emitted SVG a\n// live function of its CSS custom properties (see packages/core/src/theme.ts and\n// docs/theming.md). Every other consumer — resvg, librsvg, Inkscape,\n// ImageMagick, anything headless-Chrome-free — implements neither, and\n// rasterizes the whole theme to black (GitHub issue #456). This pass runs\n// over the finished SVG string and evaluates each expression the way a\n// browser would, against the same `<style>` declarations the renderer just\n// wrote, so the MIX percentages in packages/core/src/theme.ts stay the single source of\n// truth: nothing here re-states a number.\n//\n// Scope, deliberately narrow — this is not a CSS engine:\n// - Custom properties are read from the root `<svg style=\"--bg:…\">` and\n// from `svg { --_line: … }` declarations in `<style>` blocks. The\n// renderer only ever declares them on the root, so one flat scope is\n// exact; no cascade/specificity is modelled.\n// - `color-mix()` is evaluated only `in srgb` (the only space the\n// renderer emits) and only over colors `parseCssColor` understands\n// (hex, `rgb()`/`rgba()`, `transparent`). Anything else is left as-is.\n// - `var(--x)` with no declaration and no fallback is left untouched —\n// e.g. `font: 'var(--font-family-body)'`, a host-page variable this\n// library cannot know the value of.\n// - Element text content (labels, `<title>`) and the `data-src` source\n// stamp are never touched: only attribute values and `<style>` blocks\n// are CSS, so only those are rewritten.\n// ============================================================================\n\nimport type { DiagramColors } from '@zombie-mermaid/core'\nimport { formatCssColor, mixSrgb, parseCssColor } from '@zombie-mermaid/core'\n\n// ============================================================================\n// Expression evaluation\n// ============================================================================\n\n/**\n * Custom-property lookup with memoization and a cycle guard. Values are\n * stored raw (as declared) and evaluated on first use, so a declaration\n * can reference another declared later in the block.\n */\nclass VarScope {\n private readonly raw = new Map<string, string>()\n private readonly resolved = new Map<string, string | undefined>()\n private readonly inProgress = new Set<string>()\n\n declare(name: string, value: string): void {\n this.raw.set(name, value)\n this.resolved.delete(name)\n }\n\n /** Evaluated value of `name`, or undefined when unknown or cyclic. */\n lookup(name: string): string | undefined {\n if (this.resolved.has(name)) return this.resolved.get(name)\n const rawValue = this.raw.get(name)\n if (rawValue === undefined || this.inProgress.has(name)) return undefined\n this.inProgress.add(name)\n const value = evaluateExpression(rawValue, this).trim()\n this.inProgress.delete(name)\n // A value that still contains an unresolvable reference is treated as\n // unknown, so a `var()` pointing at it keeps its own fallback instead of\n // being replaced by something a rasterizer can't parse either.\n const usable = !/\\bvar\\(|\\bcolor-mix\\(/.test(value)\n this.resolved.set(name, usable ? value : undefined)\n return usable ? value : undefined\n }\n}\n\n/** Index of the `)` matching the `(` at `open`, or -1 if unbalanced. */\nfunction matchingParen(text: string, open: number): number {\n let depth = 0\n for (let i = open; i < text.length; i++) {\n const ch = text[i]\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/** Split on top-level commas only (commas nested in parens stay put). */\nfunction splitTopLevel(text: string): string[] {\n const parts: string[] = []\n let depth = 0\n let start = 0\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]\n if (ch === '(') depth++\n else if (ch === ')') depth--\n else if (ch === ',' && depth === 0) {\n parts.push(text.slice(start, i))\n start = i + 1\n }\n }\n parts.push(text.slice(start))\n return parts\n}\n\n/**\n * Matches the next `var(` or `color-mix(` not glued to a preceding\n * identifier character. Instantiated per `evaluateExpression` call (it's\n * recursive, and a shared global regex's `lastIndex` would be clobbered by\n * the inner call).\n */\nconst FUNCTION_START_SOURCE = /(?<![\\w-])(var|color-mix)\\(/g.source\n\n/**\n * Evaluate `var(name[, fallback])`. Returns the replacement text, or null\n * when the reference can't be resolved and should be left verbatim.\n */\nfunction evaluateVar(args: string, scope: VarScope): string | null {\n const [nameRaw, ...fallbackParts] = splitTopLevel(args)\n const name = (nameRaw ?? '').trim()\n const value = scope.lookup(name)\n if (value !== undefined) return value\n if (fallbackParts.length > 0) return fallbackParts.join(',').trim()\n return null\n}\n\n/** Parse one `color-mix()` operand: `<color> [<percentage>]`, in either order. */\nfunction parseMixOperand(\n operand: string,\n): { color: string; pct: number | undefined } | null {\n const tokens = operand.trim().split(/\\s+/)\n let color: string | undefined\n let pct: number | undefined\n for (const token of tokens) {\n if (/^[\\d.]+%$/.test(token)) {\n if (pct !== undefined) return null\n pct = Number(token.slice(0, -1))\n } else {\n if (color !== undefined) return null\n color = token\n }\n }\n if (color === undefined) return null\n return { color, pct }\n}\n\n/**\n * Evaluate `color-mix(in srgb, c1 [p1%], c2 [p2%])`. Returns the\n * replacement text, or null when the expression isn't one this module\n * can evaluate (a non-srgb space, an operand that isn't a concrete color).\n */\nfunction evaluateColorMix(args: string): string | null {\n const parts = splitTopLevel(args)\n if (parts.length !== 3) return null\n const [space, op1, op2] = parts\n if (\n space === undefined ||\n op1 === undefined ||\n op2 === undefined ||\n !/^\\s*in\\s+srgb\\s*$/i.test(space)\n ) {\n return null\n }\n const a = parseMixOperand(op1)\n const b = parseMixOperand(op2)\n if (!a || !b) return null\n const c1 = parseCssColor(a.color)\n const c2 = parseCssColor(b.color)\n if (!c1 || !c2) return null\n const mixed = mixSrgb(c1, c2, a.pct, b.pct)\n return mixed ? formatCssColor(mixed) : null\n}\n\n/**\n * Replace every evaluable `var()` / `color-mix()` in `text`, innermost\n * first. Expressions that can't be evaluated are kept verbatim (with their\n * arguments still evaluated as far as possible), and scanning resumes after\n * them, so an unknown variable can never stall the pass.\n */\nfunction evaluateExpression(text: string, scope: VarScope): string {\n let out = ''\n let cursor = 0\n const functionStart = new RegExp(FUNCTION_START_SOURCE, 'g')\n let match: RegExpExecArray | null\n while ((match = functionStart.exec(text)) !== null) {\n const fn = match[1]\n if (fn === undefined) continue\n const start = match.index\n const open = start + fn.length\n const close = matchingParen(text, open)\n if (close === -1) break\n const args = evaluateExpression(text.slice(open + 1, close), scope)\n const replacement =\n fn === 'var' ? evaluateVar(args, scope) : evaluateColorMix(args)\n out += text.slice(cursor, start)\n out += replacement ?? `${fn}(${args})`\n cursor = close + 1\n functionStart.lastIndex = cursor\n }\n return out + text.slice(cursor)\n}\n\n/**\n * Evaluate one CSS value against a flat set of custom-property\n * declarations — the expression engine behind `resolveCssColors`, exposed\n * so it can be unit-tested (and reused) without building an SVG around it.\n *\n * @example\n * evaluateCssColorValue('color-mix(in srgb, var(--fg) 50%, var(--bg))',\n * { '--fg': '#000000', '--bg': '#ffffff' }) // → '#808080'\n */\nexport function evaluateCssColorValue(\n value: string,\n declarations: Record<string, string>,\n): string {\n const scope = new VarScope()\n for (const [name, raw] of Object.entries(declarations)) {\n scope.declare(name, raw)\n }\n return evaluateExpression(value, scope)\n}\n\n// ============================================================================\n// SVG document pass\n// ============================================================================\n\n/** `--name: value` custom-property declarations inside a CSS rule body or a `style` attribute. */\nconst CUSTOM_PROPERTY_RE = /(--[\\w-]+)\\s*:\\s*([^;{}]+)/g\n\n/** `name=\"value\"` / `name='value'` attribute pairs inside a start tag. */\nconst ATTRIBUTE_RE = /(\\s)([\\w:.-]+)(=)(\"[^\"]*\"|'[^']*')/g\n\n/**\n * Attributes whose values are never CSS: the `embedSource` stamp holds the\n * diagram's own text, which may legitimately contain `var(` in a label.\n */\nconst SKIP_ATTRIBUTES = new Set(['data-src'])\n\nfunction collectDeclarations(css: string, scope: VarScope): void {\n CUSTOM_PROPERTY_RE.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = CUSTOM_PROPERTY_RE.exec(css)) !== null) {\n const [, name, value] = match\n if (name !== undefined && value !== undefined) {\n scope.declare(name, value.trim())\n }\n }\n}\n\n/** Rewrite the attribute values of one start tag (the text between `<` and `>`). */\nfunction resolveTagAttributes(tag: string, scope: VarScope): string {\n return tag.replace(\n ATTRIBUTE_RE,\n (whole, space: string, name: string, eq: string, quoted: string) => {\n if (SKIP_ATTRIBUTES.has(name)) return whole\n const quote = quoted[0] ?? '\"'\n const value = quoted.slice(1, -1)\n return `${space}${name}${eq}${quote}${evaluateExpression(value, scope)}${quote}`\n },\n )\n}\n\n/**\n * Replace every `var(--…)` and `color-mix(…)` in an SVG produced by this\n * library's renderer with its computed sRGB value (`#rrggbb`, or\n * `rgba(r, g, b, a)` when translucent).\n *\n * `colors` seeds the variable scope with the caller's palette; the SVG's\n * own declarations (the root `style` attribute and any `<style>` block)\n * are then read on top, so the derived `--_*` variables resolve through\n * exactly the `color-mix()` rules `buildStyleBlock` emitted. See the\n * module comment for what is and isn't rewritten.\n */\nexport function resolveCssColors(svg: string, colors: DiagramColors): string {\n const scope = new VarScope()\n scope.declare('--bg', colors.bg)\n scope.declare('--fg', colors.fg)\n if (colors.line) scope.declare('--line', colors.line)\n if (colors.accent) scope.declare('--accent', colors.accent)\n if (colors.muted) scope.declare('--muted', colors.muted)\n if (colors.surface) scope.declare('--surface', colors.surface)\n if (colors.border) scope.declare('--border', colors.border)\n\n // Pass 1: gather every custom-property declaration in the document —\n // `<style>` rule bodies and `style=\"…\"` attributes — before rewriting\n // anything, so a declaration can be referenced from earlier in the text.\n for (const block of svg.matchAll(/<style[^>]*>([\\s\\S]*?)<\\/style>/g)) {\n const css = block[1]\n if (css !== undefined) collectDeclarations(css, scope)\n }\n for (const attr of svg.matchAll(/\\sstyle=(\"[^\"]*\"|'[^']*')/g)) {\n const quoted = attr[1]\n if (quoted !== undefined) {\n collectDeclarations(quoted.slice(1, -1), scope)\n }\n }\n\n // Pass 2: rewrite. Walk the markup, evaluating `<style>` contents and\n // start-tag attribute values; leave text nodes untouched.\n let out = ''\n let cursor = 0\n while (cursor < svg.length) {\n const lt = svg.indexOf('<', cursor)\n if (lt === -1) break\n out += svg.slice(cursor, lt)\n\n if (svg.startsWith('<style', lt)) {\n const openEnd = svg.indexOf('>', lt)\n const closeStart = svg.indexOf('</style>', lt)\n if (openEnd === -1 || closeStart === -1) break\n out += svg.slice(lt, openEnd + 1)\n out += evaluateExpression(svg.slice(openEnd + 1, closeStart), scope)\n out += '</style>'\n cursor = closeStart + '</style>'.length\n continue\n }\n\n // Find the end of this tag, skipping `>` characters inside quoted\n // attribute values (escapeXml encodes them, but be safe for any raw\n // attribute the renderer passes through).\n let gt = -1\n let quote: string | null = null\n for (let i = lt + 1; i < svg.length; i++) {\n const ch = svg[i]\n if (quote !== null) {\n if (ch === quote) quote = null\n } else if (ch === '\"' || ch === \"'\") {\n quote = ch\n } else if (ch === '>') {\n gt = i\n break\n }\n }\n if (gt === -1) break\n const tag = svg.slice(lt, gt + 1)\n out +=\n tag.startsWith('<!') || tag.startsWith('<?') || tag.startsWith('</')\n ? tag\n : resolveTagAttributes(tag, scope)\n cursor = gt + 1\n }\n return out + svg.slice(cursor)\n}\n","/**\n * Class diagram layout engine (ELK.js).\n *\n * Each class box has 3 compartments:\n * 1. Header (class name + optional annotation)\n * 2. Attributes section\n * 3. Methods section\n */\n\nimport type { ElkNode, ElkExtendedEdge } from 'elkjs'\nimport type {\n ClassDiagram,\n ClassNode,\n ClassMember,\n PositionedClassDiagram,\n PositionedClassNode,\n PositionedClassNote,\n PositionedClassRelationship,\n} from '@zombie-mermaid/mermaid-parser'\nimport { formatClassMember } from '@zombie-mermaid/mermaid-parser'\nimport type { ClassRenderOptions } from '@zombie-mermaid/core'\nimport {\n estimateTextWidth,\n estimateMonoTextWidth,\n FONT_WEIGHTS,\n resolveFontSizes,\n} from '../styles.ts'\nimport { elkLayoutSync } from '../elk-instance.ts'\nimport {\n extractEdgePoints,\n extractEdgeLabelPosition,\n} from '../layout-engine/elk-adapter-utils.ts'\nimport {\n ELK_DIRECTION_FALLBACK,\n baseElkLayoutOptions,\n buildElkEdge,\n buildElkLeafNode,\n directionToElk,\n} from '../layout-engine/elk-graph-builder.ts'\nimport { measureMultilineText, resolveNodeStyle } from '@zombie-mermaid/core'\n\n/** Layout constants for class diagrams */\nexport const CLS = {\n padding: 40,\n boxPadX: 8,\n headerBaseHeight: 32,\n annotationHeight: 16,\n memberRowHeight: 20,\n sectionPadY: 8,\n emptySectionHeight: 8,\n minWidth: 120,\n memberFontSize: 11,\n memberFontWeight: 400,\n nodeSpacing: 40,\n layerSpacing: 60,\n /** Horizontal / vertical padding inside a note box, around its text */\n notePadX: 10,\n notePadY: 6,\n} as const\n\n/**\n * Layout id for the i-th note. A class id is a run of non-whitespace\n * (`\\S+` in the parser), so an id containing a space can never collide\n * with one; ELK treats ids as opaque strings.\n */\nexport function classNoteId(index: number): string {\n return `note ${index}`\n}\n\n/** Layout id for the dotted link joining note `noteId` to its class. */\nfunction classNoteLinkId(noteId: string): string {\n return `${noteId} link`\n}\n\ntype ClassSizeMap = Map<\n string,\n {\n width: number\n height: number\n headerHeight: number\n attrHeight: number\n methodHeight: number\n }\n>\n\n/** Size of each note box, keyed by its layout id. */\ntype NoteSizeMap = Map<string, { width: number; height: number }>\n\n/** Build ELK graph and size map from a class diagram. */\nfunction buildClassElkGraph(\n diagram: ClassDiagram,\n options: ClassRenderOptions,\n): { elkGraph: ElkNode; classSizes: ClassSizeMap; noteSizes: NoteSizeMap } {\n const classSizes: ClassSizeMap = new Map()\n const noteSizes: NoteSizeMap = new Map()\n const fontSizes = resolveFontSizes(options.fontSizes)\n\n for (const cls of diagram.classes) {\n const headerHeight = cls.annotation\n ? CLS.headerBaseHeight + CLS.annotationHeight\n : CLS.headerBaseHeight\n\n const attrHeight =\n cls.attributes.length > 0\n ? cls.attributes.length * CLS.memberRowHeight + CLS.sectionPadY\n : CLS.emptySectionHeight\n\n const methodHeight =\n cls.methods.length > 0\n ? cls.methods.length * CLS.memberRowHeight + CLS.sectionPadY\n : CLS.emptySectionHeight\n\n const headerTextW = estimateTextWidth(\n cls.label,\n fontSizes.nodeLabel,\n FONT_WEIGHTS.nodeLabel,\n )\n const maxAttrW = maxMemberWidth(cls.attributes)\n const maxMethodW = maxMemberWidth(cls.methods)\n const width = Math.max(\n CLS.minWidth,\n headerTextW + CLS.boxPadX * 2,\n maxAttrW + CLS.boxPadX * 2,\n maxMethodW + CLS.boxPadX * 2,\n )\n const height = headerHeight + attrHeight + methodHeight\n\n classSizes.set(cls.id, {\n width,\n height,\n headerHeight,\n attrHeight,\n methodHeight,\n })\n }\n\n // Iterate classSizes directly (populated above, in diagram.classes order)\n // rather than looking each class back up by id — sidesteps needing an\n // assertion or invariant check for a lookup that can't actually miss.\n const children: ElkNode[] = []\n for (const [id, size] of classSizes) {\n children.push(buildElkLeafNode(id, size))\n }\n\n // Class edge labels carry no per-label layout options — placement is set\n // once on the root graph below (`elk.edgeLabels.placement: CENTER`).\n const labelStyle = { fontSize: fontSizes.edgeLabel }\n\n const edges: ElkExtendedEdge[] = []\n for (const [i, rel] of diagram.relationships.entries()) {\n edges.push(\n buildElkEdge({\n id: `e${i}`,\n source: rel.from,\n target: rel.to,\n label: rel.label,\n labelStyle,\n }),\n )\n }\n\n // Notes. Mirrors Mermaid's classDb.getData(): every note is a node of its\n // own, and `note for X` adds an arrowless dotted edge note→class so the\n // layout keeps the two adjacent (with a DOWN layout the note lands above\n // its class, as it does in Mermaid's TB rendering). The link edges go\n // after the relationship edges so relationship indices stay positional.\n const classIds = new Set(classSizes.keys())\n for (const [i, note] of diagram.notes.entries()) {\n const id = classNoteId(i)\n const metrics = measureMultilineText(\n note.text,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n const size = {\n width: metrics.width + CLS.notePadX * 2,\n height: metrics.height + CLS.notePadY * 2,\n }\n noteSizes.set(id, size)\n children.push(buildElkLeafNode(id, size))\n if (note.forClass !== undefined && classIds.has(note.forClass)) {\n edges.push(\n buildElkEdge({\n id: classNoteLinkId(id),\n source: id,\n target: note.forClass,\n labelStyle,\n }),\n )\n }\n }\n\n const elkGraph: ElkNode = {\n id: 'root',\n layoutOptions: {\n ...baseElkLayoutOptions({\n // Class diagrams have no `direction` concept — they always lay out\n // top-down. See ELK_DIRECTION_FALLBACK for why that default is\n // per-diagram-type rather than shared with ER's.\n direction: directionToElk(undefined, ELK_DIRECTION_FALLBACK.class),\n nodeSpacing: CLS.nodeSpacing,\n layerSpacing: CLS.layerSpacing,\n padding: CLS.padding,\n }),\n 'elk.edgeLabels.placement': 'CENTER',\n },\n children,\n edges,\n }\n\n return { elkGraph, classSizes, noteSizes }\n}\n\n/** Extract positioned classes, relationships, and notes from ELK result. */\nfunction extractClassLayout(\n result: ElkNode,\n diagram: ClassDiagram,\n classSizes: ClassSizeMap,\n noteSizes: NoteSizeMap,\n): PositionedClassDiagram {\n const classLookup = new Map<string, ClassNode>()\n for (const cls of diagram.classes) classLookup.set(cls.id, cls)\n\n const positionedClasses: PositionedClassNode[] = []\n for (const child of result.children ?? []) {\n const cls = classLookup.get(child.id)\n if (cls) {\n const size = classSizes.get(cls.id)\n if (!size) {\n // Unreachable — classSizes is populated for every diagram.classes\n // entry, and classLookup/cls.id come from that same list.\n /* v8 ignore next */\n throw new Error(`Missing computed size for class \"${cls.id}\"`)\n }\n positionedClasses.push({\n id: cls.id,\n label: cls.label,\n annotation: cls.annotation,\n attributes: cls.attributes,\n methods: cls.methods,\n x: child.x ?? 0,\n y: child.y ?? 0,\n width: child.width ?? size.width,\n height: child.height ?? size.height,\n headerHeight: size.headerHeight,\n attrHeight: size.attrHeight,\n methodHeight: size.methodHeight,\n interaction: diagram.interactions.get(cls.id),\n // Same cascade the flowchart layout applies (src/layout-engine/\n // from-elk.ts): classDef default → assigned class → `style`.\n inlineStyle: resolveNodeStyle(cls.id, diagram),\n // Kept separately from inlineStyle so the class name still reaches\n // the SVG `class` attribute when it has no matching classDef.\n className: diagram.classAssignments.get(cls.id),\n })\n }\n }\n\n const relationships: PositionedClassRelationship[] = []\n const elkEdges = result.edges ?? []\n // The first diagram.relationships.length edges are the relationships, in\n // order — buildClassElkGraph creates exactly one ELK edge per relationship\n // before appending any note links.\n for (const [i, rel] of diagram.relationships.entries()) {\n const elkEdge = elkEdges[i]\n if (!elkEdge) {\n // Unreachable — ELK returns every edge it was given.\n /* v8 ignore next */\n throw new Error(`Missing ELK edge for relationship ${i}`)\n }\n\n const points = extractEdgePoints(elkEdge)\n const labelPosition = extractEdgeLabelPosition(elkEdge)\n\n relationships.push({\n from: rel.from,\n to: rel.to,\n type: rel.type,\n markerAt: rel.markerAt,\n label: rel.label,\n fromCardinality: rel.fromCardinality,\n toCardinality: rel.toCardinality,\n points,\n labelPosition,\n })\n }\n\n // Note links are matched by id rather than position — only notes whose\n // class exists got an edge, so their count isn't derivable from the note list.\n const linkEdges = new Map<string, ElkExtendedEdge>()\n for (const elkEdge of elkEdges.slice(diagram.relationships.length)) {\n linkEdges.set(elkEdge.id, elkEdge)\n }\n const childById = new Map<string, ElkNode>()\n for (const child of result.children ?? []) childById.set(child.id, child)\n\n const notes: PositionedClassNote[] = []\n for (const [i, note] of diagram.notes.entries()) {\n const id = classNoteId(i)\n const child = childById.get(id)\n const size = noteSizes.get(id)\n if (!child || !size) {\n // Unreachable — a child and a size are recorded for every note.\n /* v8 ignore next */\n throw new Error(`Missing layout for note ${i}`)\n }\n const link = linkEdges.get(classNoteLinkId(id))\n notes.push({\n id,\n text: note.text,\n ...(link && note.forClass !== undefined\n ? { forClass: note.forClass }\n : {}),\n x: child.x ?? 0,\n y: child.y ?? 0,\n width: child.width ?? size.width,\n height: child.height ?? size.height,\n ...(link ? { linkPoints: extractEdgePoints(link) } : {}),\n })\n }\n\n return {\n width: result.width ?? 600,\n height: result.height ?? 400,\n classes: positionedClasses,\n relationships,\n notes,\n }\n}\n\n/**\n * Lay out a parsed class diagram using ELK.js (synchronous).\n */\nexport function layoutClassDiagramSync(\n diagram: ClassDiagram,\n options: ClassRenderOptions = {},\n): PositionedClassDiagram {\n if (diagram.classes.length === 0 && diagram.notes.length === 0) {\n return { width: 0, height: 0, classes: [], relationships: [], notes: [] }\n }\n\n const { elkGraph, classSizes, noteSizes } = buildClassElkGraph(\n diagram,\n options,\n )\n const result = elkLayoutSync(elkGraph, options.layoutCache)\n return extractClassLayout(result, diagram, classSizes, noteSizes)\n}\n\n/** Calculate the max width of a list of class members (uses mono metrics) */\nfunction maxMemberWidth(members: ClassMember[]): number {\n if (members.length === 0) return 0\n let maxW = 0\n for (const m of members) {\n const text = formatClassMember(m)\n const w = estimateMonoTextWidth(text, CLS.memberFontSize)\n if (w > maxW) maxW = w\n }\n return maxW\n}\n","import type {\n PositionedClassDiagram,\n PositionedClassNode,\n PositionedClassNote,\n PositionedClassRelationship,\n ClassMember,\n RelationshipType,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { DiagramColors, SvgEmitOptions } from '@zombie-mermaid/core'\nimport {\n svgOpenTag,\n buildStyleBlock,\n getReadableTextColor,\n sanitizeClassName,\n renderMultilineText,\n escapeXml as escapeXmlUtil,\n escapeAttr,\n safeHref,\n} from '@zombie-mermaid/core'\nimport { withDataSrc } from '../renderer.ts'\nimport {\n FONT_SIZES,\n FONT_WEIGHTS,\n STROKE_WIDTHS,\n TEXT_BASELINE_SHIFT,\n} from '../styles.ts'\nimport type { FontSizes } from '../styles.ts'\nimport { CLS } from './layout.ts'\n\n// ============================================================================\n// Class diagram SVG renderer\n//\n// Renders positioned class diagrams to SVG.\n// All colors use CSS custom properties (var(--_xxx)) from the theme system.\n//\n// Render order:\n// 1. Relationship lines and note links (behind boxes)\n// 2. Class boxes (header + attributes + methods compartments)\n// 3. Notes (dog-eared boxes)\n// 4. Relationship labels and cardinality\n// ============================================================================\n\n/** Font sizes specific to class diagrams */\nconst CLS_FONT = {\n memberSize: 11,\n memberWeight: 400,\n annotationSize: 10,\n annotationWeight: 500,\n} as const\n\n/**\n * Render a positioned class diagram as an SVG string.\n *\n * @param colors - DiagramColors with bg/fg and optional enrichment variables.\n * @param transparent - If true, renders with transparent background.\n * @param embedSource - Original diagram source to stamp onto the root `<svg>`\n * as `data-src` (from `options.embedSource`). Omitted\n * when the option is off.\n * @param title - Accessible name (from `options.title`). See svgOpenTag() in\n * packages/core/src/theme.ts.\n * @param decorative - Marks the SVG decorative (from `options.decorative`).\n * @param linksEnabled - Whether `click`-based `<a href>` links and `<title>`\n * tooltips render (from `options.interactivity !==\n * 'none'`, see `resolveLinksEnabled` in\n * src/diagram-registry.ts).\n * Default true — matches the flowchart/state renderer.\n * @param emit - Strict-CSP controls (from `options.nonce` /\n * `options.styleAttribute`, see #216). Default: no nonce,\n * root `style` attribute on.\n */\nexport function renderClassSvg(\n diagram: PositionedClassDiagram,\n colors: DiagramColors,\n font: string = 'Inter',\n transparent: boolean = false,\n fontSizes: FontSizes = FONT_SIZES,\n embedSource?: string,\n title?: string,\n decorative?: boolean,\n linksEnabled: boolean = true,\n emit: SvgEmitOptions = {},\n): string {\n const parts: string[] = []\n\n // See #239 / packages/svg-renderer/src/renderer.ts's renderSvg: a click-based link renders as a\n // focusable <a href> inside the SVG, which role=\"img\"/aria-hidden would\n // hide from assistive tech while leaving it Tab-reachable — svgOpenTag\n // forces no root role in that case. Gated by linksEnabled too, since\n // `interactivity: 'none'` strips the <a> below.\n const hasInteractiveLinks =\n linksEnabled &&\n diagram.classes.some((c) => Boolean(safeHref(c.interaction?.href)))\n\n // SVG root with CSS variables + style block (with mono font) + defs\n parts.push(\n withDataSrc(\n svgOpenTag(\n diagram.width,\n diagram.height,\n colors,\n transparent,\n title,\n decorative,\n hasInteractiveLinks,\n emit.styleAttribute,\n ),\n embedSource,\n ),\n )\n parts.push(buildStyleBlock(font, true, emit.nonce))\n parts.push('<defs>')\n parts.push(relationshipMarkerDefs())\n parts.push('</defs>')\n\n // 1. Relationship lines and note links (rendered behind boxes)\n for (const rel of diagram.relationships) {\n parts.push(renderRelationship(rel))\n }\n for (const note of diagram.notes) {\n parts.push(renderNoteLink(note))\n }\n\n // 2. Class boxes\n for (const cls of diagram.classes) {\n parts.push(renderClassBox(cls, fontSizes, linksEnabled))\n }\n\n // 3. Notes\n for (const note of diagram.notes) {\n parts.push(renderNote(note, fontSizes))\n }\n\n // 4. Relationship labels and cardinality\n for (const rel of diagram.relationships) {\n parts.push(renderRelationshipLabels(rel, fontSizes))\n }\n\n parts.push('</svg>')\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Marker definitions\n// ============================================================================\n\n/**\n * Marker definitions for class relationship endpoints.\n * Each relationship type has a distinct marker:\n * - inheritance: hollow triangle\n * - composition: filled diamond\n * - aggregation: hollow diamond\n * - association: open arrow (simple >)\n * - dependency: open arrow (simple >)\n * - realization: hollow triangle (same as inheritance)\n *\n * Uses var(--_arrow) for fill/stroke and var(--bg) for hollow marker fills.\n */\nfunction relationshipMarkerDefs(): string {\n return (\n // Hollow triangle (inheritance, realization) — points at target\n ` <marker id=\"cls-inherit\" markerWidth=\"12\" markerHeight=\"10\" refX=\"12\" refY=\"5\" orient=\"auto-start-reverse\">` +\n `\\n <polygon points=\"0 0, 12 5, 0 10\" fill=\"var(--bg)\" stroke=\"var(--_arrow)\" stroke-width=\"1.5\" />` +\n `\\n </marker>` +\n // Filled diamond (composition) — points at source\n `\\n <marker id=\"cls-composition\" markerWidth=\"12\" markerHeight=\"10\" refX=\"0\" refY=\"5\" orient=\"auto-start-reverse\">` +\n `\\n <polygon points=\"6 0, 12 5, 6 10, 0 5\" fill=\"var(--_arrow)\" stroke=\"var(--_arrow)\" stroke-width=\"1\" />` +\n `\\n </marker>` +\n // Hollow diamond (aggregation) — points at source\n `\\n <marker id=\"cls-aggregation\" markerWidth=\"12\" markerHeight=\"10\" refX=\"0\" refY=\"5\" orient=\"auto-start-reverse\">` +\n `\\n <polygon points=\"6 0, 12 5, 6 10, 0 5\" fill=\"var(--bg)\" stroke=\"var(--_arrow)\" stroke-width=\"1.5\" />` +\n `\\n </marker>` +\n // Open arrow (association, dependency)\n `\\n <marker id=\"cls-arrow\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto-start-reverse\">` +\n `\\n <polyline points=\"0 0, 8 3, 0 6\" fill=\"none\" stroke=\"var(--_arrow)\" stroke-width=\"1.5\" />` +\n `\\n </marker>`\n )\n}\n\n// ============================================================================\n// Class box rendering\n// ============================================================================\n\n/**\n * Render a class box with 3 compartments: header, attributes, methods.\n * Wrapped in <g class=\"class-node\"> with semantic data attributes.\n *\n * @param linksEnabled - Whether a `click`-based `<a href>` link and `<title>`\n * tooltip render (from `options.interactivity !==\n * 'none'`). Default true. Mirrors renderNode() in\n * packages/svg-renderer/src/renderer.ts — see that function's docstring and\n * docs/decisions/no-script-interactivity.md for why a\n * `call`/callback binding is recorded as a data\n * attribute rather than ever invoked.\n */\nfunction renderClassBox(\n cls: PositionedClassNode,\n fontSizes: FontSizes,\n linksEnabled: boolean = true,\n): string {\n const { x, y, width, height, headerHeight, attrHeight, inlineStyle } = cls\n const parts: string[] = []\n\n const interaction = cls.interaction\n\n // Resolve box colors — inline styles (from `classDef`/`cssClass`/`style`)\n // override the CSS-variable defaults the same way renderNodeShape() in\n // packages/svg-renderer/src/renderer.ts does for flowchart nodes. With no inline style the\n // variables keep deriving from the theme via color-mix(), so dark mode is\n // untouched; a concrete `fill` is used verbatim, exactly as Mermaid does.\n const fill = escapeAttr(inlineStyle?.fill ?? 'var(--_node-fill)')\n const stroke = escapeAttr(inlineStyle?.stroke ?? 'var(--_node-stroke)')\n const strokeWidth = escapeAttr(\n inlineStyle?.['stroke-width'] ?? String(STROKE_WIDTHS.outerBox),\n )\n // Mermaid paints the whole class box one color, so a custom fill replaces\n // the header band too rather than leaving the theme's band on top of it.\n const headerFill = inlineStyle?.fill ? fill : 'var(--_group-hdr)'\n // An explicit `color:` wins; otherwise a concrete custom fill gets a\n // black/white text color chosen for contrast (see getReadableTextColor,\n // issue #55). Left undefined when neither applies so the syntax-colored\n // member tspans keep their theme defaults.\n const textColor = inlineStyle?.color\n ? escapeAttr(inlineStyle.color)\n : inlineStyle?.fill\n ? escapeAttr(getReadableTextColor(inlineStyle.fill, 'var(--_text)'))\n : undefined\n\n // Semantic wrapper with class metadata\n // data-id: class identifier\n // data-label: class name\n // data-annotation: stereotype (interface, abstract, etc.)\n const annotationAttr = cls.annotation\n ? ` data-annotation=\"${escapeAttr(cls.annotation)}\"`\n : ''\n // A style class from `cssClass`/`class A name`/`:::name` is emitted onto\n // the group so external CSS can target it, after the same allowlist the\n // flowchart renderer applies.\n const safeClassName = sanitizeClassName(cls.className)\n const groupAttrs = [\n `class=\"${safeClassName ? `class-node ${safeClassName}` : 'class-node'}\"`,\n `data-id=\"${escapeAttr(cls.id)}\"`,\n `data-label=\"${escapeAttr(cls.label)}\"`,\n ]\n // `click ClassName call fn()` is parsed, never invoked, and never written\n // into the markup — see renderNode() in packages/svg-renderer/src/renderer.ts for the rationale.\n parts.push(`<g ${groupAttrs.join(' ')}${annotationAttr}>`)\n\n // An href becomes a real SVG link, which needs no script to work.\n // `interactivity: 'none'` strips it — a link is meaningless in\n // print/rasterized output, the target that level is meant for.\n const href = linksEnabled ? safeHref(interaction?.href) : undefined\n if (href) {\n const targetAttr = interaction?.target\n ? ` target=\"${escapeAttr(interaction.target)}\"`\n : ''\n parts.push(` <a href=\"${escapeAttr(href)}\"${targetAttr}>`)\n }\n\n if (linksEnabled && interaction?.tooltip) {\n // <title> is SVG's native tooltip — no script, no CSS. Gated by\n // linksEnabled alongside href above: both come from the same `click`\n // statement, and 'none' strips both.\n parts.push(` <title>${escapeXml(interaction.tooltip)}</title>`)\n }\n\n // Outer rectangle (full box)\n parts.push(\n ` <rect x=\"${x}\" y=\"${y}\" width=\"${width}\" height=\"${height}\" ` +\n `rx=\"0\" ry=\"0\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${strokeWidth}\" />`,\n )\n\n // Header background\n parts.push(\n ` <rect x=\"${x}\" y=\"${y}\" width=\"${width}\" height=\"${headerHeight}\" ` +\n `rx=\"0\" ry=\"0\" fill=\"${headerFill}\" stroke=\"${stroke}\" stroke-width=\"${strokeWidth}\" />`,\n )\n\n // Annotation (<<interface>>, <<abstract>>, etc.)\n let nameY = y + headerHeight / 2\n if (cls.annotation) {\n const annotY = y + 12\n parts.push(\n ` <text x=\"${x + width / 2}\" y=\"${annotY}\" text-anchor=\"middle\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n `font-size=\"${CLS_FONT.annotationSize}\" font-weight=\"${CLS_FONT.annotationWeight}\" ` +\n `font-style=\"italic\" fill=\"${textColor ?? 'var(--_text-muted)'}\">&lt;&lt;${escapeXml(cls.annotation)}&gt;&gt;</text>`,\n )\n nameY = y + headerHeight / 2 + 6\n }\n\n // Class name (supports multi-line via <br> tags)\n parts.push(\n ' ' +\n renderMultilineText(\n cls.label,\n x + width / 2,\n nameY,\n fontSizes.nodeLabel,\n `text-anchor=\"middle\" font-size=\"${fontSizes.nodeLabel}\" font-weight=\"700\" fill=\"${textColor ?? 'var(--_text)'}\"`,\n ),\n )\n\n // Divider line between header and attributes\n const attrTop = y + headerHeight\n parts.push(\n ` <line x1=\"${x}\" y1=\"${attrTop}\" x2=\"${x + width}\" y2=\"${attrTop}\" ` +\n `stroke=\"${stroke}\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />`,\n )\n\n // Attributes\n const memberRowH = 20\n for (let i = 0; i < cls.attributes.length; i++) {\n const member = cls.attributes[i]!\n const memberY = attrTop + 4 + i * memberRowH + memberRowH / 2\n parts.push(' ' + renderMember(member, x + CLS.boxPadX, memberY, textColor))\n }\n\n // Divider line between attributes and methods\n const methodTop = attrTop + attrHeight\n parts.push(\n ` <line x1=\"${x}\" y1=\"${methodTop}\" x2=\"${x + width}\" y2=\"${methodTop}\" ` +\n `stroke=\"${stroke}\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />`,\n )\n\n // Methods\n for (let i = 0; i < cls.methods.length; i++) {\n const member = cls.methods[i]!\n const memberY = methodTop + 4 + i * memberRowH + memberRowH / 2\n parts.push(' ' + renderMember(member, x + CLS.boxPadX, memberY, textColor))\n }\n\n if (href) parts.push(' </a>')\n parts.push('</g>')\n\n return parts.join('\\n')\n}\n\n/**\n * Render a single class member with syntax highlighting.\n * Uses <tspan> elements to color each part of the member differently:\n * - visibility symbol (+/-/#/~) → textFaint\n * - member name (incl. parens for methods) → textSecondary\n * - colon separator → textFaint\n * - type annotation → textMuted\n *\n * @param textColor - A resolved custom text color (from `style ... color:` or\n * derived from a custom `fill`). When given, every tspan\n * uses it: the per-part theme tints are tuned against the\n * theme's own node fill and can't be assumed readable on\n * an arbitrary user-chosen background.\n */\nfunction renderMember(\n member: ClassMember,\n x: number,\n y: number,\n textColor?: string,\n): string {\n const fontStyle = member.isAbstract ? ' font-style=\"italic\"' : ''\n const decoration = member.isStatic ? ' text-decoration=\"underline\"' : ''\n\n const faint = textColor ?? 'var(--_text-faint)'\n const secondary = textColor ?? 'var(--_text-sec)'\n const muted = textColor ?? 'var(--_text-muted)'\n\n // Build tspan parts for syntax-highlighted member text\n const spans: string[] = []\n\n if (member.visibility) {\n spans.push(\n `<tspan fill=\"${faint}\">${escapeXml(member.visibility)} </tspan>`,\n )\n }\n\n // Add parentheses for methods to distinguish from attributes, including parameters if present\n const displayName = member.isMethod\n ? `${member.name}(${member.params || ''})`\n : member.name\n // False positive: displayName is passed through escapeXml() (see packages/core/src/multiline-utils.ts),\n // which escapes &, <, >, \", ' before interpolation, so this is not raw/unescaped HTML.\n spans.push(`<tspan fill=\"${secondary}\">${escapeXml(displayName)}</tspan>`) // nosemgrep: javascript.express.security.injection.raw-html-format.raw-html-format\n\n if (member.type) {\n spans.push(`<tspan fill=\"${faint}\">: </tspan>`)\n spans.push(`<tspan fill=\"${muted}\">${escapeXml(member.type)}</tspan>`)\n }\n\n return (\n `<text x=\"${x}\" y=\"${y}\" class=\"mono\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n `font-size=\"${CLS_FONT.memberSize}\" font-weight=\"${CLS_FONT.memberWeight}\"${fontStyle}${decoration}>` +\n `${spans.join('')}</text>`\n )\n}\n\n// ============================================================================\n// Note rendering\n// ============================================================================\n\n/** Size of the folded corner on a note box, in px */\nconst NOTE_FOLD = 6\n\n/**\n * Render a note as a dog-eared box: a polygon with its top-right corner\n * clipped plus a small fold triangle — the same shape the sequence renderer\n * draws for its notes (../sequence/renderer.ts renderNote), so notes look\n * alike across diagram types. Wrapped in <g class=\"class-note\"> with\n * `data-for` naming the class it's attached to, if any.\n */\nfunction renderNote(note: PositionedClassNote, fontSizes: FontSizes): string {\n const { x, y, width: w, height: h } = note\n const forAttr =\n note.forClass !== undefined\n ? ` data-for=\"${escapeAttr(note.forClass)}\"`\n : ''\n\n // Note body: (x,y) → (x+w-fold,y) → (x+w,y+fold) → (x+w,y+h) → (x,y+h)\n const bodyPoints = [\n `${x},${y}`,\n `${x + w - NOTE_FOLD},${y}`,\n `${x + w},${y + NOTE_FOLD}`,\n `${x + w},${y + h}`,\n `${x},${y + h}`,\n ].join(' ')\n const foldPoints =\n `${x + w - NOTE_FOLD},${y} ${x + w},${y + NOTE_FOLD} ` +\n `${x + w - NOTE_FOLD},${y + NOTE_FOLD}`\n\n return (\n `<g class=\"class-note\"${forAttr}>` +\n `\\n <polygon points=\"${bodyPoints}\" ` +\n `fill=\"var(--bg)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />` +\n `\\n <polygon points=\"${foldPoints}\" ` +\n `fill=\"var(--_inner-stroke)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />` +\n `\\n ${renderMultilineText(\n note.text,\n x + w / 2,\n y + h / 2,\n fontSizes.edgeLabel,\n `font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n )}` +\n `\\n</g>`\n )\n}\n\n/**\n * Render the dotted, arrowless link from a `note for X` note to its class\n * (Mermaid's `pattern: 'dotted'` note edge). Empty for a free note.\n */\nfunction renderNoteLink(note: PositionedClassNote): string {\n if (!note.linkPoints || note.linkPoints.length < 2) return ''\n const pathData = note.linkPoints.map((p) => `${p.x},${p.y}`).join(' ')\n const forAttr =\n note.forClass !== undefined\n ? ` data-for=\"${escapeAttr(note.forClass)}\"`\n : ''\n return (\n `<polyline class=\"class-note-link\"${forAttr} points=\"${pathData}\" ` +\n `fill=\"none\" stroke=\"var(--_line)\" stroke-width=\"${STROKE_WIDTHS.connector}\" stroke-dasharray=\"2 3\" />`\n )\n}\n\n// ============================================================================\n// Relationship rendering\n// ============================================================================\n\n/**\n * Render a relationship line with appropriate markers and semantic attributes.\n * Includes data-* attributes for programmatic inspection.\n */\nfunction renderRelationship(rel: PositionedClassRelationship): string {\n if (rel.points.length < 2) return ''\n\n const pathData = rel.points.map((p) => `${p.x},${p.y}`).join(' ')\n const isDashed = rel.type === 'dependency' || rel.type === 'realization'\n const dashArray = isDashed ? ' stroke-dasharray=\"6 4\"' : ''\n\n // Determine markers based on relationship type and which end has the marker\n const markers = getRelationshipMarkers(rel.type, rel.markerAt)\n\n // Build semantic data attributes for relationship inspection:\n // - class=\"class-relationship\": CSS targeting\n // - data-from/data-to: source and target class IDs\n // - data-type: relationship type (inheritance, composition, etc.)\n // - data-marker-at: which end has the marker (from/to)\n // - data-from-cardinality/data-to-cardinality: multiplicity if present\n // - data-label: relationship label if present\n const dataAttrs = [\n 'class=\"class-relationship\"',\n `data-from=\"${escapeAttr(rel.from)}\"`,\n `data-to=\"${escapeAttr(rel.to)}\"`,\n `data-type=\"${rel.type}\"`,\n `data-marker-at=\"${rel.markerAt}\"`,\n ]\n if (rel.label) {\n dataAttrs.push(`data-label=\"${escapeAttr(rel.label)}\"`)\n }\n if (rel.fromCardinality) {\n dataAttrs.push(`data-from-cardinality=\"${escapeAttr(rel.fromCardinality)}\"`)\n }\n if (rel.toCardinality) {\n dataAttrs.push(`data-to-cardinality=\"${escapeAttr(rel.toCardinality)}\"`)\n }\n\n return (\n `<polyline ${dataAttrs.join(' ')} points=\"${pathData}\" fill=\"none\" stroke=\"var(--_line)\" ` +\n `stroke-width=\"${STROKE_WIDTHS.connector}\"${dashArray}${markers} />`\n )\n}\n\n/**\n * Get marker-start/marker-end attributes for a relationship type.\n * Uses `markerAt` from the parser to place the marker on the correct end:\n * - 'from' → marker-start (prefix arrows like `<|--`, `*--`, `o--`)\n * - 'to' → marker-end (suffix arrows like `..|>`, `-->`, `--*`)\n */\nfunction getRelationshipMarkers(\n type: RelationshipType,\n markerAt: 'from' | 'to',\n): string {\n const markerId = getMarkerDefId(type)\n if (!markerId) return ''\n\n if (markerAt === 'from') {\n return ` marker-start=\"url(#${markerId})\"`\n } else {\n return ` marker-end=\"url(#${markerId})\"`\n }\n}\n\n/** Map relationship type to its SVG marker definition ID */\nfunction getMarkerDefId(type: RelationshipType): string | null {\n switch (type) {\n case 'inheritance':\n case 'realization':\n return 'cls-inherit'\n case 'composition':\n return 'cls-composition'\n case 'aggregation':\n return 'cls-aggregation'\n case 'association':\n case 'dependency':\n return 'cls-arrow'\n default:\n return null\n }\n}\n\n/** Render relationship labels and cardinality text (supports multi-line) */\nfunction renderRelationshipLabels(\n rel: PositionedClassRelationship,\n fontSizes: FontSizes,\n): string {\n if (!rel.label && !rel.fromCardinality && !rel.toCardinality) return ''\n if (rel.points.length < 2) return ''\n\n const parts: string[] = []\n\n // Label — prefer layout-computed position (collision-aware), fall back to midpoint\n if (rel.label) {\n const pos = rel.labelPosition ?? midpoint(rel.points)\n parts.push(\n renderMultilineText(\n rel.label,\n pos.x,\n pos.y - 8,\n fontSizes.edgeLabel,\n `font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n }\n\n // From cardinality (near start)\n if (rel.fromCardinality && rel.points.length >= 2) {\n const p = rel.points[0]!\n const next = rel.points[1]!\n const offset = cardinalityOffset(p, next)\n parts.push(\n renderMultilineText(\n rel.fromCardinality,\n p.x + offset.x,\n p.y + offset.y,\n fontSizes.edgeLabel,\n `font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n }\n\n // To cardinality (near end)\n if (rel.toCardinality && rel.points.length >= 2) {\n const p = rel.points[rel.points.length - 1]!\n const prev = rel.points[rel.points.length - 2]!\n const offset = cardinalityOffset(p, prev)\n parts.push(\n renderMultilineText(\n rel.toCardinality,\n p.x + offset.x,\n p.y + offset.y,\n fontSizes.edgeLabel,\n `font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n }\n\n return parts.join('\\n')\n}\n\n/** Get the midpoint of a point array */\nfunction midpoint(points: Array<{ x: number; y: number }>): {\n x: number\n y: number\n} {\n if (points.length === 0) return { x: 0, y: 0 }\n const mid = Math.floor(points.length / 2)\n return points[mid]!\n}\n\n/** Calculate offset for cardinality label perpendicular to edge direction */\nfunction cardinalityOffset(\n from: { x: number; y: number },\n to: { x: number; y: number },\n): { x: number; y: number } {\n const dx = to.x - from.x\n const dy = to.y - from.y\n // Place label perpendicular to the edge, 14px away\n if (Math.abs(dx) > Math.abs(dy)) {\n // Mostly horizontal — offset vertically\n return { x: dx > 0 ? 14 : -14, y: -10 }\n }\n // Mostly vertical — offset horizontally\n return { x: -14, y: dy > 0 ? 14 : -14 }\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n// Use shared escapeXml from multiline-utils\nconst escapeXml = escapeXmlUtil\n","/**\n * ER diagram layout engine (ELK.js).\n *\n * Each entity box has:\n * 1. Header (entity name)\n * 2. Attribute rows (type, name, keys)\n */\n\nimport type { ElkNode, ElkExtendedEdge } from 'elkjs'\nimport type {\n ErDiagram,\n ErEntity,\n PositionedErDiagram,\n PositionedErEntity,\n PositionedErRelationship,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { ErRenderOptions } from '@zombie-mermaid/core'\nimport {\n estimateTextWidth,\n estimateMonoTextWidth,\n FONT_WEIGHTS,\n resolveFontSizes,\n} from '../styles.ts'\nimport { elkLayoutSync } from '../elk-instance.ts'\nimport { extractEdgePoints } from '../layout-engine/elk-adapter-utils.ts'\nimport {\n ELK_DIRECTION_FALLBACK,\n baseElkLayoutOptions,\n buildElkEdge,\n buildElkLeafNode,\n directionToElk,\n} from '../layout-engine/elk-graph-builder.ts'\n\n/** Layout constants for ER diagrams */\nconst ER = {\n padding: 40,\n boxPadX: 14,\n headerHeight: 34,\n rowHeight: 22,\n minWidth: 140,\n attrFontSize: 11,\n attrFontWeight: 400,\n nodeSpacing: 70,\n layerSpacing: 90,\n} as const\n\ntype EntitySizeMap = Map<string, { width: number; height: number }>\n\n/** Build ELK graph and size map from an ER diagram. */\nfunction buildErElkGraph(\n diagram: ErDiagram,\n options: ErRenderOptions,\n): { elkGraph: ElkNode; entitySizes: EntitySizeMap } {\n const entitySizes: EntitySizeMap = new Map()\n const fontSizes = resolveFontSizes(options.fontSizes)\n\n for (const entity of diagram.entities) {\n const headerTextW = estimateTextWidth(\n entity.label,\n fontSizes.nodeLabel,\n FONT_WEIGHTS.nodeLabel,\n )\n let maxAttrW = 0\n for (const attr of entity.attributes) {\n const attrText = `${attr.type} ${attr.name}${attr.keys.length > 0 ? ' ' + attr.keys.join(',') : ''}`\n const w = estimateMonoTextWidth(attrText, ER.attrFontSize)\n if (w > maxAttrW) maxAttrW = w\n }\n const width = Math.max(\n ER.minWidth,\n headerTextW + ER.boxPadX * 2,\n maxAttrW + ER.boxPadX * 2,\n )\n const height =\n ER.headerHeight + Math.max(entity.attributes.length, 1) * ER.rowHeight\n entitySizes.set(entity.id, { width, height })\n }\n\n // Iterate entitySizes directly (populated above, in diagram.entities order)\n // rather than looking each entity back up by id — sidesteps needing an\n // assertion or invariant check for a lookup that can't actually miss.\n const children: ElkNode[] = []\n for (const [id, size] of entitySizes) {\n children.push(buildElkLeafNode(id, size))\n }\n\n // ER edge labels carry no per-label layout options — placement is set\n // once on the root graph below (`elk.edgeLabels.placement: CENTER`).\n const labelStyle = { fontSize: fontSizes.edgeLabel }\n\n const edges: ElkExtendedEdge[] = []\n for (const [i, rel] of diagram.relationships.entries()) {\n edges.push(\n buildElkEdge({\n id: `e${i}`,\n source: rel.entity1,\n target: rel.entity2,\n label: rel.label,\n labelStyle,\n }),\n )\n }\n\n const elkGraph: ElkNode = {\n id: 'root',\n layoutOptions: {\n ...baseElkLayoutOptions({\n // A source with no `direction` statement lays out left-to-right —\n // ER's own default, unlike flowchart/class's DOWN. See\n // ELK_DIRECTION_FALLBACK.\n direction: directionToElk(diagram.direction, ELK_DIRECTION_FALLBACK.er),\n nodeSpacing: ER.nodeSpacing,\n layerSpacing: ER.layerSpacing,\n padding: ER.padding,\n }),\n 'elk.edgeLabels.placement': 'CENTER',\n },\n children,\n edges,\n }\n\n return { elkGraph, entitySizes }\n}\n\n/** Extract positioned entities and relationships from ELK result. */\nfunction extractErLayout(\n result: ElkNode,\n diagram: ErDiagram,\n entitySizes: EntitySizeMap,\n): PositionedErDiagram {\n const entityLookup = new Map<string, ErEntity>()\n for (const entity of diagram.entities) entityLookup.set(entity.id, entity)\n\n const positionedEntities: PositionedErEntity[] = []\n for (const child of result.children ?? []) {\n const entity = entityLookup.get(child.id)\n if (entity) {\n const size = entitySizes.get(entity.id)\n if (!size) {\n // Unreachable — entitySizes is populated for every diagram.entities\n // entry, and entityLookup/entity.id come from that same list.\n /* v8 ignore next */\n throw new Error(`Missing computed size for entity \"${entity.id}\"`)\n }\n positionedEntities.push({\n id: entity.id,\n label: entity.label,\n attributes: entity.attributes,\n x: child.x ?? 0,\n y: child.y ?? 0,\n width: child.width ?? size.width,\n height: child.height ?? size.height,\n headerHeight: ER.headerHeight,\n rowHeight: ER.rowHeight,\n })\n }\n }\n\n const relationships: PositionedErRelationship[] = []\n for (const [i, elkEdge] of (result.edges ?? []).entries()) {\n // diagram.relationships[i] is guaranteed by construction: buildErElkGraph\n // creates exactly one ELK edge per relationship, in the same order.\n const rel = diagram.relationships[i]!\n\n const points = extractEdgePoints(elkEdge)\n\n relationships.push({\n entity1: rel.entity1,\n entity2: rel.entity2,\n cardinality1: rel.cardinality1,\n cardinality2: rel.cardinality2,\n label: rel.label,\n identifying: rel.identifying,\n points,\n })\n }\n\n return {\n width: result.width ?? 600,\n height: result.height ?? 400,\n entities: positionedEntities,\n relationships,\n }\n}\n\n/**\n * Lay out a parsed ER diagram using ELK.js (synchronous).\n */\nexport function layoutErDiagramSync(\n diagram: ErDiagram,\n options: ErRenderOptions = {},\n): PositionedErDiagram {\n if (diagram.entities.length === 0) {\n return { width: 0, height: 0, entities: [], relationships: [] }\n }\n\n const { elkGraph, entitySizes } = buildErElkGraph(diagram, options)\n const result = elkLayoutSync(elkGraph, options.layoutCache)\n return extractErLayout(result, diagram, entitySizes)\n}\n","import type {\n PositionedErDiagram,\n PositionedErEntity,\n PositionedErRelationship,\n ErAttribute,\n Cardinality,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { DiagramColors, SvgEmitOptions } from '@zombie-mermaid/core'\nimport {\n svgOpenTag,\n buildStyleBlock,\n renderMultilineText,\n escapeXml as escapeXmlUtil,\n escapeAttr,\n measureMultilineText,\n} from '@zombie-mermaid/core'\nimport { withDataSrc } from '../renderer.ts'\nimport {\n FONT_SIZES,\n FONT_WEIGHTS,\n STROKE_WIDTHS,\n estimateTextWidth,\n TEXT_BASELINE_SHIFT,\n} from '../styles.ts'\nimport type { FontSizes } from '../styles.ts'\n\n// ============================================================================\n// ER diagram SVG renderer\n//\n// Renders positioned ER diagrams to SVG.\n// All colors use CSS custom properties (var(--_xxx)) from the theme system.\n//\n// Render order:\n// 1. Relationship lines (behind boxes)\n// 2. Entity boxes (header + attribute rows)\n// 3. Cardinality markers (crow's foot notation)\n// 4. Relationship labels\n// ============================================================================\n\n/** Font sizes specific to ER diagrams */\nconst ER_FONT = {\n attrSize: 11,\n attrWeight: 400,\n keySize: 9,\n keyWeight: 600,\n} as const\n\n/**\n * Render a positioned ER diagram as an SVG string.\n *\n * @param colors - DiagramColors with bg/fg and optional enrichment variables.\n * @param transparent - If true, renders with transparent background.\n * @param embedSource - Original diagram source to stamp onto the root `<svg>`\n * as `data-src` (from `options.embedSource`). Omitted\n * when the option is off.\n * @param title - Accessible name (from `options.title`). See svgOpenTag() in\n * packages/core/src/theme.ts.\n * @param decorative - Marks the SVG decorative (from `options.decorative`).\n * @param emit - Strict-CSP controls (from `options.nonce` /\n * `options.styleAttribute`, see #216). Default: no nonce,\n * root `style` attribute on.\n */\nexport function renderErSvg(\n diagram: PositionedErDiagram,\n colors: DiagramColors,\n font: string = 'Inter',\n transparent: boolean = false,\n fontSizes: FontSizes = FONT_SIZES,\n embedSource?: string,\n title?: string,\n decorative?: boolean,\n emit: SvgEmitOptions = {},\n): string {\n const parts: string[] = []\n\n // SVG root with CSS variables + style block (with mono font) + defs\n parts.push(\n withDataSrc(\n svgOpenTag(\n diagram.width,\n diagram.height,\n colors,\n transparent,\n title,\n decorative,\n undefined,\n emit.styleAttribute,\n ),\n embedSource,\n ),\n )\n parts.push(buildStyleBlock(font, true, emit.nonce))\n parts.push('<defs>')\n parts.push('</defs>') // No marker defs — we draw crow's foot inline\n\n // 1. Relationship lines\n for (const rel of diagram.relationships) {\n parts.push(renderRelationshipLine(rel))\n }\n\n // 2. Entity boxes\n for (const entity of diagram.entities) {\n parts.push(renderEntityBox(entity, fontSizes))\n }\n\n // 3. Cardinality markers at relationship endpoints\n for (const rel of diagram.relationships) {\n parts.push(renderCardinality(rel))\n }\n\n // 4. Relationship labels\n for (const rel of diagram.relationships) {\n parts.push(renderRelationshipLabel(rel, fontSizes))\n }\n\n parts.push('</svg>')\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Entity box rendering\n// ============================================================================\n\n/**\n * Render an entity box with header and attribute rows.\n * Wrapped in <g class=\"entity\"> with semantic data attributes.\n */\nfunction renderEntityBox(\n entity: PositionedErEntity,\n fontSizes: FontSizes,\n): string {\n const {\n id,\n x,\n y,\n width,\n height,\n headerHeight,\n rowHeight,\n label,\n attributes,\n } = entity\n const parts: string[] = []\n\n // Semantic wrapper with entity metadata\n parts.push(\n `<g class=\"entity\" data-id=\"${escapeAttr(id)}\" data-label=\"${escapeAttr(label)}\">`,\n )\n\n // Outer rectangle\n parts.push(\n ` <rect x=\"${x}\" y=\"${y}\" width=\"${width}\" height=\"${height}\" ` +\n `rx=\"0\" ry=\"0\" fill=\"var(--_node-fill)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n\n // Header background\n parts.push(\n ` <rect x=\"${x}\" y=\"${y}\" width=\"${width}\" height=\"${headerHeight}\" ` +\n `rx=\"0\" ry=\"0\" fill=\"var(--_group-hdr)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n\n // Entity name (supports multi-line via <br> tags)\n parts.push(\n ' ' +\n renderMultilineText(\n label,\n x + width / 2,\n y + headerHeight / 2,\n fontSizes.nodeLabel,\n `text-anchor=\"middle\" font-size=\"${fontSizes.nodeLabel}\" font-weight=\"700\" fill=\"var(--_text)\"`,\n ),\n )\n\n // Divider\n const attrTop = y + headerHeight\n parts.push(\n ` <line x1=\"${x}\" y1=\"${attrTop}\" x2=\"${x + width}\" y2=\"${attrTop}\" ` +\n `stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />`,\n )\n\n // Attribute rows\n for (let i = 0; i < attributes.length; i++) {\n const attr = attributes[i]!\n const rowY = attrTop + i * rowHeight + rowHeight / 2\n parts.push(\n ' ' + renderAttribute(attr, x, rowY, width).replace(/\\n/g, '\\n '),\n )\n }\n\n // Empty row placeholder when no attributes\n if (attributes.length === 0) {\n parts.push(\n ` <text x=\"${x + width / 2}\" y=\"${attrTop + rowHeight / 2}\" text-anchor=\"middle\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n `font-size=\"${ER_FONT.attrSize}\" fill=\"var(--_text-faint)\" font-style=\"italic\">(no attributes)</text>`,\n )\n }\n\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n/**\n * Render a single attribute row with monospace syntax highlighting.\n * Layout: [PK badge] type name (left-aligned in mono, name right-aligned)\n * Uses <tspan> elements for per-part coloring, matching the class diagram style.\n *\n * Key badge uses var(--_key-badge) for background tint.\n * Comments are shown as tooltips via SVG <title> element.\n */\nfunction renderAttribute(\n attr: ErAttribute,\n boxX: number,\n y: number,\n boxWidth: number,\n): string {\n const parts: string[] = []\n\n // Wrap in a group if there's a comment (for tooltip support)\n const hasComment = attr.comment && attr.comment.length > 0\n if (attr.comment && attr.comment.length > 0) {\n // Replace <br> with newlines for tooltip display\n const tooltipText = attr.comment.replace(/<br\\s*\\/?>/gi, '\\n')\n parts.push(`<g><title>${escapeXml(tooltipText)}</title>`)\n }\n\n // Key badges on the left (keep proportional font — they're visual tags, not code)\n let keyWidth = 0\n if (attr.keys.length > 0) {\n const keyText = attr.keys.join(',')\n keyWidth =\n estimateTextWidth(keyText, ER_FONT.keySize, ER_FONT.keyWeight) + 8\n parts.push(\n `<rect x=\"${boxX + 6}\" y=\"${y - 7}\" width=\"${keyWidth}\" height=\"14\" rx=\"2\" ry=\"2\" ` +\n `fill=\"var(--_key-badge)\" />`,\n )\n parts.push(\n `<text x=\"${boxX + 6 + keyWidth / 2}\" y=\"${y}\" text-anchor=\"middle\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n `font-size=\"${ER_FONT.keySize}\" font-weight=\"${ER_FONT.keyWeight}\" fill=\"var(--_text-sec)\">${attr.keys.join(',')}</text>`,\n )\n }\n\n // Type (left-aligned after keys, monospace with syntax highlighting)\n const typeX = boxX + 8 + (keyWidth > 0 ? keyWidth + 6 : 0)\n parts.push(\n `<text x=\"${typeX}\" y=\"${y}\" class=\"mono\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n `font-size=\"${ER_FONT.attrSize}\" font-weight=\"${ER_FONT.attrWeight}\">` +\n `<tspan fill=\"var(--_text-muted)\">${escapeXml(attr.type)}</tspan></text>`,\n )\n\n // Name (right-aligned, monospace with syntax highlighting)\n const nameX = boxX + boxWidth - 8\n parts.push(\n `<text x=\"${nameX}\" y=\"${y}\" class=\"mono\" text-anchor=\"end\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n `font-size=\"${ER_FONT.attrSize}\" font-weight=\"${ER_FONT.attrWeight}\">` +\n `<tspan fill=\"var(--_text-sec)\">${escapeXml(attr.name)}</tspan></text>`,\n )\n\n // Close the group if we opened one\n if (hasComment) {\n parts.push('</g>')\n }\n\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Relationship rendering\n// ============================================================================\n\n/**\n * Render a relationship line with semantic data attributes.\n */\nfunction renderRelationshipLine(rel: PositionedErRelationship): string {\n if (rel.points.length < 2) return ''\n\n const pathData = rel.points.map((p) => `${p.x},${p.y}`).join(' ')\n const dashArray = !rel.identifying ? ' stroke-dasharray=\"6 4\"' : ''\n\n // Semantic data attributes for relationship inspection\n const labelAttr = rel.label ? ` data-label=\"${escapeAttr(rel.label)}\"` : ''\n const dataAttrs = [\n 'class=\"er-relationship\"',\n `data-entity1=\"${escapeAttr(rel.entity1)}\"`,\n `data-entity2=\"${escapeAttr(rel.entity2)}\"`,\n `data-cardinality1=\"${rel.cardinality1}\"`,\n `data-cardinality2=\"${rel.cardinality2}\"`,\n `data-identifying=\"${rel.identifying}\"`,\n ]\n\n return (\n `<polyline ${dataAttrs.join(' ')}${labelAttr} points=\"${pathData}\" fill=\"none\" stroke=\"var(--_line)\" ` +\n `stroke-width=\"${STROKE_WIDTHS.connector}\"${dashArray} />`\n )\n}\n\n/** Render a relationship label at the midpoint (supports multi-line) */\nfunction renderRelationshipLabel(\n rel: PositionedErRelationship,\n fontSizes: FontSizes,\n): string {\n if (!rel.label || rel.points.length < 2) return ''\n\n const mid = midpoint(rel.points)\n const metrics = measureMultilineText(\n rel.label,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n\n // Background pill for readability\n const bgW = metrics.width + 8\n const bgH = metrics.height + 6\n\n return (\n `<rect x=\"${mid.x - bgW / 2}\" y=\"${mid.y - bgH / 2}\" width=\"${bgW}\" height=\"${bgH}\" rx=\"2\" ry=\"2\" ` +\n `fill=\"var(--bg)\" stroke=\"var(--_inner-stroke)\" stroke-width=\"0.5\" />` +\n `\\n${renderMultilineText(\n rel.label,\n mid.x,\n mid.y,\n fontSizes.edgeLabel,\n `text-anchor=\"middle\" font-size=\"${fontSizes.edgeLabel}\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n )}`\n )\n}\n\n/**\n * Render crow's foot cardinality markers at both endpoints of a relationship.\n *\n * Crow's foot notation:\n * 'one': ─║─ (single vertical line)\n * 'zero-one': ─o║─ (circle + single line)\n * 'many': ─╢─ (crow's foot + single line)\n * 'zero-many': ─o╣─ (circle + crow's foot)\n */\nfunction renderCardinality(rel: PositionedErRelationship): string {\n if (rel.points.length < 2) return ''\n const parts: string[] = []\n\n // Entity1 side (first point, direction toward second point)\n const p1 = rel.points[0]!\n const p2 = rel.points[1]!\n parts.push(renderCrowsFoot(p1, p2, rel.cardinality1))\n\n // Entity2 side (last point, direction toward second-to-last point)\n const pN = rel.points[rel.points.length - 1]!\n const pN1 = rel.points[rel.points.length - 2]!\n parts.push(renderCrowsFoot(pN, pN1, rel.cardinality2))\n\n return parts.join('\\n')\n}\n\n/**\n * Render a crow's foot marker at a given endpoint.\n * `point` is the endpoint, `toward` gives the direction the line comes from.\n */\nfunction renderCrowsFoot(\n point: { x: number; y: number },\n toward: { x: number; y: number },\n cardinality: Cardinality,\n): string {\n const parts: string[] = []\n const sw = STROKE_WIDTHS.connector + 0.25\n\n // Calculate direction from toward → point (unit vector)\n const dx = point.x - toward.x\n const dy = point.y - toward.y\n const len = Math.sqrt(dx * dx + dy * dy)\n if (len === 0) return ''\n const ux = dx / len\n const uy = dy / len\n\n // Perpendicular direction\n const px = -uy\n const py = ux\n\n // Marker sits 4px from the endpoint, extending 12px back along the edge\n const tipX = point.x - ux * 4\n const tipY = point.y - uy * 4\n const backX = point.x - ux * 16\n const backY = point.y - uy * 16\n\n // Single line: always present for 'one' and part of others\n const hasOneLine = cardinality === 'one' || cardinality === 'zero-one'\n const hasCrowsFoot = cardinality === 'many' || cardinality === 'zero-many'\n const hasCircle = cardinality === 'zero-one' || cardinality === 'zero-many'\n\n // Draw single vertical line (perpendicular to edge) at the tip\n if (hasOneLine) {\n const halfW = 6\n parts.push(\n `<line x1=\"${tipX + px * halfW}\" y1=\"${tipY + py * halfW}\" ` +\n `x2=\"${tipX - px * halfW}\" y2=\"${tipY - py * halfW}\" ` +\n `stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n // Second line slightly back for \"exactly one\" emphasis\n const line2X = tipX - ux * 4\n const line2Y = tipY - uy * 4\n parts.push(\n `<line x1=\"${line2X + px * halfW}\" y1=\"${line2Y + py * halfW}\" ` +\n `x2=\"${line2X - px * halfW}\" y2=\"${line2Y - py * halfW}\" ` +\n `stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n }\n\n // Crow's foot (three lines fanning out from tip)\n if (hasCrowsFoot) {\n const fanW = 7\n // Center line\n const cfTipX = tipX\n const cfTipY = tipY\n // Three lines from tip to back, fanning out\n parts.push(\n // Top fan line\n `<line x1=\"${cfTipX + px * fanW}\" y1=\"${cfTipY + py * fanW}\" ` +\n `x2=\"${backX}\" y2=\"${backY}\" ` +\n `stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n parts.push(\n // Center line\n `<line x1=\"${cfTipX}\" y1=\"${cfTipY}\" ` +\n `x2=\"${backX}\" y2=\"${backY}\" ` +\n `stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n parts.push(\n // Bottom fan line\n `<line x1=\"${cfTipX - px * fanW}\" y1=\"${cfTipY - py * fanW}\" ` +\n `x2=\"${backX}\" y2=\"${backY}\" ` +\n `stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n }\n\n // Circle (for zero variants)\n if (hasCircle) {\n const circleOffset = hasCrowsFoot ? 20 : 12\n const circleX = point.x - ux * circleOffset\n const circleY = point.y - uy * circleOffset\n parts.push(\n `<circle cx=\"${circleX}\" cy=\"${circleY}\" r=\"4\" ` +\n `fill=\"var(--bg)\" stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n }\n\n return parts.join('\\n')\n}\n\n/** Compute the arc-length midpoint of a polyline path.\n * Walks along each segment, finds the point at exactly 50% of total path length.\n * This ensures the label sits ON the path even for orthogonal routes with bends,\n * unlike the naive first/last geometric center which floats in space for L/Z shapes. */\nfunction midpoint(points: Array<{ x: number; y: number }>): {\n x: number\n y: number\n} {\n if (points.length === 0) return { x: 0, y: 0 }\n if (points.length === 1) return points[0]!\n\n // Compute total path length\n let totalLen = 0\n for (let i = 1; i < points.length; i++) {\n const dx = points[i]!.x - points[i - 1]!.x\n const dy = points[i]!.y - points[i - 1]!.y\n totalLen += Math.sqrt(dx * dx + dy * dy)\n }\n\n if (totalLen === 0) return points[0]!\n\n // Walk to 50% of total length, interpolating within the segment that crosses the halfway mark\n const halfLen = totalLen / 2\n let walked = 0\n for (let i = 1; i < points.length; i++) {\n const dx = points[i]!.x - points[i - 1]!.x\n const dy = points[i]!.y - points[i - 1]!.y\n const segLen = Math.sqrt(dx * dx + dy * dy)\n if (walked + segLen >= halfLen) {\n const t = segLen > 0 ? (halfLen - walked) / segLen : 0\n return {\n x: points[i - 1]!.x + dx * t,\n y: points[i - 1]!.y + dy * t,\n }\n }\n walked += segLen\n }\n\n return points[points.length - 1]!\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n// Use shared escapeXml from multiline-utils\nconst escapeXml = escapeXmlUtil\n","import type {\n SequenceDiagram,\n PositionedSequenceDiagram,\n PositionedActor,\n Lifeline,\n PositionedMessage,\n Activation,\n PositionedBlock,\n PositionedNote,\n PositionedParticipantBox,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { RenderOptions, SequenceRenderOptions } from '@zombie-mermaid/core'\nimport { estimateTextWidth, FONT_WEIGHTS, resolveFontSizes } from '../styles.ts'\n\n// ============================================================================\n// Sequence diagram layout engine\n//\n// Custom timeline-based layout (no ELK — sequence diagrams aren't graphs).\n//\n// Layout strategy:\n// 1. Space actors horizontally based on label widths + min gap\n// 2. Stack messages vertically in chronological order\n// 3. Track activation boxes via a stack\n// 4. Position blocks (loop/alt/opt) as background rectangles\n// 5. Position notes next to their target actors\n// ============================================================================\n\n/** Layout constants specific to sequence diagrams */\nconst SEQ = {\n /** Padding around the entire diagram */\n padding: 30,\n /** Minimum gap between actor centers */\n actorGap: 140,\n /** Actor box height */\n actorHeight: 40,\n /** Horizontal padding inside actor boxes */\n actorPadX: 16,\n /** Vertical space between actor boxes and first message */\n headerGap: 20,\n /** Vertical space per message row */\n messageRowHeight: 40,\n /** Extra vertical space for self-messages (they loop back) */\n selfMessageHeight: 30,\n /** Activation box width (narrow rectangle on lifeline) */\n activationWidth: 10,\n /** Block padding (loop/alt borders) */\n blockPadX: 10,\n blockPadTop: 40,\n blockPadBottom: 8,\n /** Extra vertical space before the first message in a block (room for the header label) */\n blockHeaderExtra: 28,\n /** Extra vertical space before a message at a divider boundary (room for else/and label) */\n dividerExtra: 24,\n /** Note dimensions */\n noteWidth: 60,\n notePadX: 12,\n notePadY: 6,\n noteGap: 10,\n /** Gap between a message arrow and a note positioned directly after it */\n noteOffsetAfterMessage: 8,\n /** Gap between consecutively stacked notes */\n noteStackGap: 4,\n /**\n * `box … end` group padding: horizontal clearance beyond the outermost\n * member's box, vertical clearance above the actor boxes (under the label\n * band) and below the lifeline bottoms.\n */\n boxPad: 10,\n} as const\n\n/**\n * Height of the label band at the top of a `box … end` group, in px — the\n * label's font size plus vertical breathing room. Shared with the renderer\n * so the label lands centred in the band the layout reserved for it.\n */\nexport function boxLabelHeight(labelFontSize: number): number {\n return labelFontSize + 8\n}\n\n/** Resolved sequence-layout config — same shape as {@link SEQ} but mutable numbers. */\ntype SeqConfig = { [K in keyof typeof SEQ]: number }\n\n/**\n * Merge user-provided sequence-layout overrides over the {@link SEQ} defaults.\n * Any field left unspecified (or `undefined`) falls back to its default.\n */\nfunction resolveSeqOptions(overrides?: RenderOptions['sequence']): SeqConfig {\n return {\n ...SEQ,\n actorHeight: overrides?.actorHeight ?? SEQ.actorHeight,\n headerGap: overrides?.headerGap ?? SEQ.headerGap,\n messageRowHeight: overrides?.messageRowHeight ?? SEQ.messageRowHeight,\n noteOffsetAfterMessage:\n overrides?.noteOffsetAfterMessage ?? SEQ.noteOffsetAfterMessage,\n noteStackGap: overrides?.noteStackGap ?? SEQ.noteStackGap,\n }\n}\n\n/**\n * Lay out a parsed sequence diagram.\n * Returns a fully positioned diagram ready for SVG rendering.\n */\nexport function layoutSequenceDiagram(\n diagram: SequenceDiagram,\n options: SequenceRenderOptions = {},\n): PositionedSequenceDiagram {\n if (diagram.actors.length === 0) {\n return {\n width: 0,\n height: 0,\n actors: [],\n lifelines: [],\n messages: [],\n activations: [],\n blocks: [],\n notes: [],\n boxes: [],\n }\n }\n\n const seq = resolveSeqOptions(options.sequence)\n const fontSizes = resolveFontSizes(options.fontSizes)\n\n // 1. Calculate actor widths and assign horizontal positions (center X)\n const actorWidths = diagram.actors.map((a) => {\n const textW = estimateTextWidth(\n a.label,\n fontSizes.nodeLabel,\n FONT_WEIGHTS.nodeLabel,\n )\n return Math.max(textW + seq.actorPadX * 2, 80)\n })\n\n // Build actor center X positions with minimum gap\n const actorCenterX: number[] = []\n let currentX = seq.padding + actorWidths[0]! / 2\n for (let i = 0; i < diagram.actors.length; i++) {\n if (i > 0) {\n const minGap = Math.max(\n seq.actorGap,\n (actorWidths[i - 1]! + actorWidths[i]!) / 2 + 40,\n )\n currentX += minGap\n }\n actorCenterX.push(currentX)\n }\n\n // Build actor ID → index lookup\n const actorIndex = new Map<string, number>()\n for (let i = 0; i < diagram.actors.length; i++) {\n actorIndex.set(diagram.actors[i]!.id, i)\n }\n\n // 2. Position actors at the top. A `box … end` group draws a full-height\n // background starting at the top padding, so when any (non-empty) box\n // exists the actor row moves down to leave room for the box's top\n // padding and — if any box has a label — its label band. Mermaid does\n // the same (`bumpVerticalPos(boxes[0].textMaxHeight)`).\n const renderedBoxes = diagram.boxes.filter((b) => b.actorIds.length > 0)\n const anyBoxLabel = renderedBoxes.some((b) => b.label !== '')\n const boxTopInset =\n renderedBoxes.length === 0\n ? 0\n : seq.boxPad + (anyBoxLabel ? boxLabelHeight(fontSizes.edgeLabel) : 0)\n const actorY = seq.padding + boxTopInset\n const actors: PositionedActor[] = diagram.actors.map((a, i) => ({\n id: a.id,\n label: a.label,\n type: a.type,\n x: actorCenterX[i]!,\n y: actorY,\n width: actorWidths[i]!,\n height: seq.actorHeight,\n }))\n\n // Box backgrounds span from the outermost member's left edge to the\n // outermost member's right edge (plus padding) — any participant declared\n // between two members sits visually inside, as in Mermaid. Heights are\n // filled in once the diagram bottom is known (step 8 below).\n const boxes: PositionedParticipantBox[] = renderedBoxes.map((box) => {\n const idxs = box.actorIds.map((id) => actorIndex.get(id) ?? 0)\n const lo = Math.min(...idxs)\n const hi = Math.max(...idxs)\n const left = actorCenterX[lo]! - actorWidths[lo]! / 2 - seq.boxPad\n const right = actorCenterX[hi]! + actorWidths[hi]! / 2 + seq.boxPad\n const positioned: PositionedParticipantBox = {\n label: box.label,\n x: left,\n y: seq.padding,\n width: right - left,\n height: 0,\n }\n if (box.color !== undefined) positioned.color = box.color\n return positioned\n })\n\n // 3. Stack messages vertically\n let messageY = actorY + seq.actorHeight + seq.headerGap\n const messages: PositionedMessage[] = []\n\n // Pre-scan blocks to determine which message indices need extra vertical\n // space for block headers (e.g. \"alt [Valid credentials]\") or divider\n // labels (e.g. \"[else Invalid]\"). Without this, messages inside blocks\n // overlap with the header/divider text that sits above them.\n const extraSpaceBefore = new Map<number, number>()\n for (const block of diagram.blocks) {\n // First message in the block needs room for the block header label\n const prev = extraSpaceBefore.get(block.startIndex) ?? 0\n extraSpaceBefore.set(block.startIndex, Math.max(prev, seq.blockHeaderExtra))\n\n // Each divider (else/and) needs room for the divider label\n for (const div of block.dividers) {\n const prevDiv = extraSpaceBefore.get(div.index) ?? 0\n extraSpaceBefore.set(div.index, Math.max(prevDiv, seq.dividerExtra))\n }\n }\n\n // Pre-group notes by the message index they follow, so we can position\n // them inline during the message stacking loop (avoids overlap bugs).\n const notesByAfterIndex = new Map<number, typeof diagram.notes>()\n for (const note of diagram.notes) {\n const list = notesByAfterIndex.get(note.afterIndex) ?? []\n list.push(note)\n notesByAfterIndex.set(note.afterIndex, list)\n }\n const positionedNotes: PositionedNote[] = []\n\n // Handle notes that appear before the first message (afterIndex === -1)\n const notesBeforeFirstMsg = notesByAfterIndex.get(-1)\n if (notesBeforeFirstMsg && notesBeforeFirstMsg.length > 0) {\n let noteY = messageY\n for (const note of notesBeforeFirstMsg) {\n const noteW = Math.max(\n seq.noteWidth,\n estimateTextWidth(\n note.text,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n ) +\n seq.notePadX * 2,\n )\n const noteH = fontSizes.edgeLabel + seq.notePadY * 2\n\n // X positioning based on actor position and note type\n const firstActorIdx = actorIndex.get(note.actorIds[0] ?? '') ?? 0\n let noteX: number\n if (note.position === 'left') {\n noteX =\n actorCenterX[firstActorIdx]! -\n actorWidths[firstActorIdx]! / 2 -\n noteW -\n seq.noteGap\n } else if (note.position === 'right') {\n noteX =\n actorCenterX[firstActorIdx]! +\n actorWidths[firstActorIdx]! / 2 +\n seq.noteGap\n } else {\n // over — center between first and last actor\n if (note.actorIds.length > 1) {\n const lastActorIdx =\n actorIndex.get(note.actorIds[note.actorIds.length - 1] ?? '') ??\n firstActorIdx\n noteX =\n (actorCenterX[firstActorIdx]! + actorCenterX[lastActorIdx]!) / 2 -\n noteW / 2\n } else {\n noteX = actorCenterX[firstActorIdx]! - noteW / 2\n }\n }\n\n positionedNotes.push({\n text: note.text,\n x: noteX,\n y: noteY,\n width: noteW,\n height: noteH,\n position: note.position,\n actors: note.actorIds,\n })\n\n noteY += noteH + seq.noteStackGap\n }\n\n messageY = Math.max(messageY, noteY + seq.messageRowHeight / 2)\n }\n\n // Track activation stack per actor: array of { startY, depth } objects\n // Depth is used to offset nested activations horizontally for visual clarity\n const activationStacks = new Map<\n string,\n { startY: number; depth: number }[]\n >()\n const activations: Activation[] = []\n const nestingOffset = 4 // Horizontal offset per nesting level\n\n /** Open an activation bar on `actorId` at row `y` (stacks for nesting). */\n function startActivation(actorId: string, y: number): void {\n const stack = activationStacks.get(actorId) ?? []\n activationStacks.set(actorId, stack)\n const depth = stack.length // Current depth before pushing\n stack.push({ startY: y, depth })\n }\n\n /**\n * Close the innermost open activation on `actorId` at row `y`. A\n * deactivate with nothing open is ignored rather than fatal (Mermaid\n * itself errors here; this renderer has always been lenient about it).\n */\n function endActivation(actorId: string, y: number): void {\n const stack = activationStacks.get(actorId)\n if (!stack || stack.length === 0) return\n const { startY, depth } = stack.pop()!\n const idx = actorIndex.get(actorId) ?? 0\n // Offset nested activations to the right for visual distinction\n const xOffset = depth * nestingOffset\n activations.push({\n actorId,\n x: actorCenterX[idx]! - seq.activationWidth / 2 + xOffset,\n topY: startY,\n bottomY: y,\n width: seq.activationWidth,\n })\n }\n\n /**\n * Standalone `activate`/`deactivate` statements, grouped by the message\n * they follow. Applied at that message's row — the same row the `+`/`-`\n * shorthand on that message would use — through the same two helpers, so\n * the two spellings can't drift apart.\n */\n const activationEventsByAfterIndex = new Map<\n number,\n typeof diagram.activations\n >()\n for (const event of diagram.activations) {\n const list = activationEventsByAfterIndex.get(event.afterIndex) ?? []\n list.push(event)\n activationEventsByAfterIndex.set(event.afterIndex, list)\n }\n function applyActivationEvents(afterIndex: number, y: number): void {\n for (const event of activationEventsByAfterIndex.get(afterIndex) ?? []) {\n if (event.kind === 'start') startActivation(event.actorId, y)\n else endActivation(event.actorId, y)\n }\n }\n\n // Events before the first message open at the top of the message area.\n applyActivationEvents(-1, messageY)\n\n for (let msgIdx = 0; msgIdx < diagram.messages.length; msgIdx++) {\n const msg = diagram.messages[msgIdx]!\n const fromIdx = actorIndex.get(msg.from) ?? 0\n const toIdx = actorIndex.get(msg.to) ?? 0\n const isSelf = msg.from === msg.to\n\n // Add extra vertical space if this message sits below a block header or divider\n const extra = extraSpaceBefore.get(msgIdx) ?? 0\n if (extra > 0) messageY += extra\n\n const x1 = actorCenterX[fromIdx]!\n let x2 = actorCenterX[toIdx]!\n\n // A `create participant` message ends at the near edge of the box it\n // creates rather than at the lifeline centre — the box sits on this row\n // (see the created-actor placement after this loop), so an arrow to the\n // centre would run underneath it. Mermaid's renderer makes the same\n // adjustment (`receiverAdjustment(actor, width / 2)`).\n const createsRecipient =\n !isSelf && diagram.actors[toIdx]!.createdAt === msgIdx\n if (createsRecipient) {\n const halfW = actorWidths[toIdx]! / 2\n x2 += x1 < x2 ? -halfW : halfW\n }\n\n messages.push({\n from: msg.from,\n to: msg.to,\n label: msg.label,\n lineStyle: msg.lineStyle,\n arrowHead: msg.arrowHead,\n x1,\n x2,\n y: messageY,\n isSelf,\n bidirectional: msg.bidirectional ?? false,\n seqNumber: msg.seqNumber,\n })\n\n // Handle activation - track nesting depth for visual offset. The `+`\n // shorthand activates the recipient, `-` deactivates the sender (see\n // parser.ts); standalone statements following this message apply at\n // the same row, after the shorthand, in source order.\n if (msg.activate) startActivation(msg.to, messageY)\n if (msg.deactivate) endActivation(msg.from, messageY)\n applyActivationEvents(msgIdx, messageY)\n\n // Advance messageY past the message itself\n messageY += isSelf\n ? seq.selfMessageHeight + seq.messageRowHeight\n : seq.messageRowHeight\n // The created box is centred on this row, so its lower half hangs\n // below the arrow — give the next row room to clear it (Mermaid bumps\n // by the same half-height after a creating message).\n if (createsRecipient) messageY += seq.actorHeight / 2\n\n // Position notes that appear after this message.\n // Notes start below the self-message loop (if self) or below the arrow,\n // and consecutive notes stack vertically. If notes extend beyond the\n // normal message advance, push messageY further so subsequent messages\n // don't overlap.\n const notesForMsg = notesByAfterIndex.get(msgIdx)\n if (notesForMsg && notesForMsg.length > 0) {\n // Self-message loops extend selfMessageHeight below msg.y;\n // normal arrows sit at msg.y with no extension below.\n const selfLoopExtra = isSelf ? seq.selfMessageHeight : 0\n let noteY =\n messages[msgIdx]!.y + selfLoopExtra + seq.noteOffsetAfterMessage\n\n for (const note of notesForMsg) {\n const noteW = Math.max(\n seq.noteWidth,\n estimateTextWidth(\n note.text,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n ) +\n seq.notePadX * 2,\n )\n const noteH = fontSizes.edgeLabel + seq.notePadY * 2\n\n // X positioning based on actor position and note type\n const firstActorIdx = actorIndex.get(note.actorIds[0] ?? '') ?? 0\n let noteX: number\n if (note.position === 'left') {\n noteX =\n actorCenterX[firstActorIdx]! -\n actorWidths[firstActorIdx]! / 2 -\n noteW -\n seq.noteGap\n } else if (note.position === 'right') {\n noteX =\n actorCenterX[firstActorIdx]! +\n actorWidths[firstActorIdx]! / 2 +\n seq.noteGap\n } else {\n // over — center between first and last actor\n if (note.actorIds.length > 1) {\n const lastActorIdx =\n actorIndex.get(note.actorIds[note.actorIds.length - 1] ?? '') ??\n firstActorIdx\n noteX =\n (actorCenterX[firstActorIdx]! + actorCenterX[lastActorIdx]!) / 2 -\n noteW / 2\n } else {\n noteX = actorCenterX[firstActorIdx]! - noteW / 2\n }\n }\n\n positionedNotes.push({\n text: note.text,\n x: noteX,\n y: noteY,\n width: noteW,\n height: noteH,\n position: note.position,\n actors: note.actorIds,\n })\n\n noteY += noteH + seq.noteStackGap // Stack next note below with gap\n }\n\n // Push messageY forward if notes extended beyond the normal advance.\n // Add half a row height so the next message's label (rendered at msg.y - 6)\n // has clearance from the last note's bottom edge.\n messageY = Math.max(messageY, noteY + seq.messageRowHeight / 2)\n }\n }\n\n // Close any unclosed activations (preserving depth for offset)\n for (const [actorId, stack] of activationStacks) {\n for (const { startY, depth } of stack) {\n const idx = actorIndex.get(actorId) ?? 0\n const xOffset = depth * nestingOffset\n activations.push({\n actorId,\n x: actorCenterX[idx]! - seq.activationWidth / 2 + xOffset,\n topY: startY,\n bottomY: messageY - seq.messageRowHeight / 2,\n width: seq.activationWidth,\n })\n }\n }\n\n // 4. Position blocks (loop/alt/opt)\n const blocks: PositionedBlock[] = diagram.blocks.map((block) => {\n // Block spans from the Y of startIndex to endIndex messages\n const startMsg = messages[block.startIndex]\n const endMsg = messages[block.endIndex]\n const blockTop = (startMsg?.y ?? messageY) - seq.blockPadTop\n const blockBottom = (endMsg?.y ?? messageY) + seq.blockPadBottom + 12\n\n // Block width spans all actors involved in its messages\n const involvedActors = new Set<number>()\n for (let mi = block.startIndex; mi <= block.endIndex; mi++) {\n const m = diagram.messages[mi]\n if (m) {\n involvedActors.add(actorIndex.get(m.from) ?? 0)\n involvedActors.add(actorIndex.get(m.to) ?? 0)\n }\n }\n // Fallback: span all actors if none involved\n if (involvedActors.size === 0) {\n for (let ai = 0; ai < diagram.actors.length; ai++) involvedActors.add(ai)\n }\n const minIdx = Math.min(...involvedActors)\n const maxIdx = Math.max(...involvedActors)\n const blockLeft =\n actorCenterX[minIdx]! - actorWidths[minIdx]! / 2 - seq.blockPadX\n const blockRight =\n actorCenterX[maxIdx]! + actorWidths[maxIdx]! / 2 + seq.blockPadX\n\n // Position dividers — offset from message Y so the divider label text\n // (rendered at divider.y + 14 in the renderer) clears the message label\n // (rendered at msg.y - 6).\n //\n // Default offset 28 gives ~8px baseline clearance, which is sufficient\n // when the divider label (left-aligned at block edge) and message label\n // (centered between actors) don't share horizontal space. When they DO\n // overlap horizontally (e.g. long divider labels like \"[Account locked]\"\n // next to centered message labels like \"403 Forbidden\"), we increase the\n // offset to 36 so text bounding boxes have ~5px visual clearance.\n const dividers = block.dividers.map((d) => {\n const msg = messages[d.index]\n const msgY = msg?.y ?? messageY\n let offset = 28\n\n // Dynamic overlap detection: increase offset when the divider label\n // and message label occupy the same horizontal region, which would\n // cause vertical text overlap at the default 8px baseline gap.\n if (d.label && msg?.label) {\n const divLabelText = `[${d.label}]`\n const divLabelW = estimateTextWidth(\n divLabelText,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n const divLabelLeft = blockLeft + 8\n const divLabelRight = divLabelLeft + divLabelW\n\n const msgLabelW = estimateTextWidth(\n msg.label,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n // Self-messages render labels at x1 + 36 (left-aligned); normal\n // messages center the label between the two actor lifelines.\n const msgLabelLeft = msg.isSelf\n ? msg.x1 + 36\n : (msg.x1 + msg.x2) / 2 - msgLabelW / 2\n const msgLabelRight = msgLabelLeft + msgLabelW\n\n if (divLabelRight > msgLabelLeft && divLabelLeft < msgLabelRight) {\n offset = 36\n }\n }\n\n return { y: msgY - offset, label: d.label }\n })\n\n return {\n type: block.type,\n label: block.label,\n x: blockLeft,\n y: blockTop,\n width: blockRight - blockLeft,\n height: blockBottom - blockTop,\n dividers,\n }\n })\n\n // 5. Notes — already positioned inline during the message stacking loop\n // (step 3) to properly account for self-message loops and vertical stacking.\n const notes = positionedNotes\n\n // 6. Bounding-box post-processing\n //\n // Notes positioned \"left of\" the first actor or \"right of\" the last actor\n // can extend beyond the actor-based viewport. Compute the true bounding box\n // across all positioned elements, then shift everything right if anything\n // extends left of the desired padding margin and expand the width to fit.\n const diagramBottom = messageY + seq.padding\n\n // Find global X extents across actors, blocks, notes, and message labels\n let globalMinX: number = seq.padding // actors already start at seq.padding\n let globalMaxX = 0\n for (const a of actors) {\n globalMinX = Math.min(globalMinX, a.x - a.width / 2)\n globalMaxX = Math.max(globalMaxX, a.x + a.width / 2)\n }\n for (const b of blocks) {\n globalMinX = Math.min(globalMinX, b.x)\n globalMaxX = Math.max(globalMaxX, b.x + b.width)\n }\n for (const n of notes) {\n globalMinX = Math.min(globalMinX, n.x)\n globalMaxX = Math.max(globalMaxX, n.x + n.width)\n }\n for (const b of boxes) {\n globalMinX = Math.min(globalMinX, b.x)\n globalMaxX = Math.max(globalMaxX, b.x + b.width)\n }\n // Include self-message labels in bounding box — they extend to the right of the actor\n // and could be clipped if not accounted for in the SVG width\n for (const m of messages) {\n if (m.isSelf && m.label) {\n const loopW = 30 // matches renderer loopW\n const labelPadding = 8\n const labelLeft = m.x1 + loopW + labelPadding\n const labelWidth = estimateTextWidth(\n m.label,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n globalMaxX = Math.max(globalMaxX, labelLeft + labelWidth + 8) // +8 for safety margin\n }\n }\n\n // If elements extend left of the desired padding, shift everything right\n const shiftX = globalMinX < seq.padding ? seq.padding - globalMinX : 0\n if (shiftX > 0) {\n for (const a of actors) a.x += shiftX\n for (const m of messages) {\n m.x1 += shiftX\n m.x2 += shiftX\n }\n for (const act of activations) act.x += shiftX\n for (const b of blocks) {\n b.x += shiftX\n }\n for (const n of notes) n.x += shiftX\n for (const b of boxes) b.x += shiftX\n // Also shift actor center X array (used for lifelines below)\n for (let i = 0; i < actorCenterX.length; i++) actorCenterX[i]! += shiftX\n }\n\n // 7. Participant lifecycle: a created participant's box moves from the\n // header down to its creating message's row, centred on the arrow\n // (Mermaid: `actor.starty = lineStartY - actor.height / 2`), and a\n // destroyed participant's lifeline stops at its destroying message.\n // Both indices come from the parser, which already verified the\n // message exists and involves the actor — the `?? messageY` fallbacks\n // only guard the type, not a reachable state.\n for (let i = 0; i < diagram.actors.length; i++) {\n const createdAt = diagram.actors[i]!.createdAt\n if (createdAt !== undefined) {\n actors[i]!.y = (messages[createdAt]?.y ?? messageY) - seq.actorHeight / 2\n }\n }\n\n // 8. Calculate final lifelines (after shift so X positions are correct)\n const lifelines: Lifeline[] = diagram.actors.map((a, i) => {\n const destroyedAt = a.destroyedAt\n const lifeline: Lifeline = {\n actorId: a.id,\n x: actorCenterX[i]!,\n topY: actors[i]!.y + seq.actorHeight,\n bottomY:\n destroyedAt === undefined\n ? diagramBottom - seq.padding\n : (messages[destroyedAt]?.y ?? messageY),\n }\n if (destroyedAt !== undefined) lifeline.destroyed = true\n return lifeline\n })\n\n // Box backgrounds run from the top padding to just under the lifeline\n // bottoms (boxPad < padding, so this stays inside the diagram height).\n for (const b of boxes) {\n b.height = diagramBottom - seq.padding + seq.boxPad - b.y\n }\n\n // 9. Calculate diagram dimensions from the bounding box\n const diagramWidth = globalMaxX + shiftX + seq.padding\n const diagramHeight = diagramBottom\n\n return {\n width: Math.max(diagramWidth, 200),\n height: Math.max(diagramHeight, 100),\n actors,\n lifelines,\n messages,\n activations,\n blocks,\n notes,\n boxes,\n }\n}\n","import type {\n PositionedSequenceDiagram,\n PositionedActor,\n Lifeline,\n PositionedMessage,\n Activation,\n PositionedBlock,\n PositionedNote,\n PositionedParticipantBox,\n} from '@zombie-mermaid/mermaid-parser'\nimport { boxLabelHeight } from './layout.ts'\nimport type { DiagramColors, SvgEmitOptions } from '@zombie-mermaid/core'\nimport {\n svgOpenTag,\n buildStyleBlock,\n renderMultilineText,\n escapeAttr,\n} from '@zombie-mermaid/core'\nimport { withDataSrc } from '../renderer.ts'\nimport {\n FONT_SIZES,\n FONT_WEIGHTS,\n STROKE_WIDTHS,\n ARROW_HEAD,\n estimateTextWidth,\n} from '../styles.ts'\nimport type { FontSizes } from '../styles.ts'\n\n// ============================================================================\n// Sequence diagram SVG renderer\n//\n// Renders a positioned sequence diagram to SVG string.\n// All colors use CSS custom properties (var(--_xxx)) from the theme system.\n//\n// Render order (back to front):\n// 0. Participant-group backgrounds (box … end)\n// 1. Block backgrounds (loop/alt/opt)\n// 2. Lifelines (dashed vertical lines)\n// 3. Activation boxes\n// 4. Messages (arrows with labels)\n// 5. Notes\n// 6. Actor boxes (at top)\n// ============================================================================\n\n/**\n * Render a positioned sequence diagram as an SVG string.\n *\n * @param colors - DiagramColors with bg/fg and optional enrichment variables.\n * @param transparent - If true, renders with transparent background.\n * @param embedSource - Original diagram source to stamp onto the root `<svg>`\n * as `data-src` (from `options.embedSource`). Omitted\n * when the option is off.\n * @param title - Accessible name (from `options.title`). See svgOpenTag() in\n * packages/core/src/theme.ts.\n * @param decorative - Marks the SVG decorative (from `options.decorative`).\n * @param emit - Strict-CSP controls (from `options.nonce` /\n * `options.styleAttribute`, see #216). Default: no nonce,\n * root `style` attribute on.\n */\nexport function renderSequenceSvg(\n diagram: PositionedSequenceDiagram,\n colors: DiagramColors,\n font: string = 'Inter',\n transparent: boolean = false,\n fontSizes: FontSizes = FONT_SIZES,\n embedSource?: string,\n title?: string,\n decorative?: boolean,\n emit: SvgEmitOptions = {},\n): string {\n const parts: string[] = []\n\n // SVG root with CSS variables + style block + defs\n parts.push(\n withDataSrc(\n svgOpenTag(\n diagram.width,\n diagram.height,\n colors,\n transparent,\n title,\n decorative,\n undefined,\n emit.styleAttribute,\n ),\n embedSource,\n ),\n )\n parts.push(buildStyleBlock(font, false, emit.nonce))\n parts.push('<defs>')\n\n // Arrow marker definitions\n parts.push(arrowMarkerDefs())\n parts.push('</defs>')\n\n // 0. Participant-group backgrounds (box … end), behind everything\n for (const box of diagram.boxes) {\n parts.push(renderParticipantBox(box, fontSizes))\n }\n\n // 1. Block backgrounds (loop/alt/opt rectangles)\n for (const block of diagram.blocks) {\n parts.push(renderBlock(block, fontSizes))\n }\n\n // 2. Lifelines (dashed vertical lines from actor to bottom)\n for (const lifeline of diagram.lifelines) {\n parts.push(renderLifeline(lifeline))\n }\n\n // 3. Activation boxes\n for (const activation of diagram.activations) {\n parts.push(renderActivation(activation))\n }\n\n // 4. Messages (horizontal arrows with labels)\n for (const message of diagram.messages) {\n parts.push(renderMessage(message, fontSizes))\n }\n\n // 5. Notes\n for (const note of diagram.notes) {\n parts.push(renderNote(note, fontSizes))\n }\n\n // 6. Actor boxes at top (rendered last so they're on top)\n for (const actor of diagram.actors) {\n parts.push(renderActor(actor, fontSizes))\n }\n\n parts.push('</svg>')\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Arrow marker definitions\n// ============================================================================\n\nfunction arrowMarkerDefs(): string {\n const w = ARROW_HEAD.width\n const h = ARROW_HEAD.height\n return (\n ` <marker id=\"seq-arrow\" markerWidth=\"${w}\" markerHeight=\"${h}\" refX=\"${w}\" refY=\"${h / 2}\" orient=\"auto-start-reverse\">` +\n `\\n <polygon points=\"0 0, ${w} ${h / 2}, 0 ${h}\" fill=\"var(--_arrow)\" />` +\n `\\n </marker>` +\n // Open arrow head (just lines, no fill)\n `\\n <marker id=\"seq-arrow-open\" markerWidth=\"${w}\" markerHeight=\"${h}\" refX=\"${w}\" refY=\"${h / 2}\" orient=\"auto-start-reverse\">` +\n `\\n <polyline points=\"0 0, ${w} ${h / 2}, 0 ${h}\" fill=\"none\" stroke=\"var(--_arrow)\" stroke-width=\"1\" />` +\n `\\n </marker>`\n )\n}\n\n// ============================================================================\n// Component renderers\n// ============================================================================\n\n/**\n * Render an actor box (participant = rectangle, actor = stick figure).\n * Wrapped in <g class=\"actor\"> with semantic data attributes.\n */\nfunction renderActor(actor: PositionedActor, fontSizes: FontSizes): string {\n const { id, x, y, width, height, label, type } = actor\n const parts: string[] = []\n\n // Semantic wrapper with actor metadata\n parts.push(\n `<g class=\"actor\" data-id=\"${escapeAttr(id)}\" data-label=\"${escapeAttr(label)}\" data-type=\"${type}\">`,\n )\n\n if (type === 'actor') {\n // Circle-person icon: outer circle + head circle + shoulders arc.\n // Defined in a 24×24 coordinate space, scaled to 90% of the actor box height\n // and centered both horizontally and vertically within the box.\n // Stroke width is inverse-scaled so the visual thickness matches STROKE_WIDTHS.outerBox.\n const s = (height / 24) * 0.9\n const tx = x - 12 * s // center icon horizontally on actor.x\n const ty = y + (height - 24 * s) / 2 // center icon vertically in actor box\n const sw = STROKE_WIDTHS.outerBox / s // compensate for scale transform\n const iconStroke = 'var(--_line)' // use line color for actor icon strokes\n\n parts.push(\n ` <g transform=\"translate(${tx},${ty}) scale(${s})\">` +\n // Outer circle\n `\\n <path d=\"M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z\" fill=\"none\" stroke=\"${iconStroke}\" stroke-width=\"${sw}\" />` +\n // Head\n `\\n <path d=\"M15 10C15 11.6569 13.6569 13 12 13C10.3431 13 9 11.6569 9 10C9 8.34315 10.3431 7 12 7C13.6569 7 15 8.34315 15 10Z\" fill=\"none\" stroke=\"${iconStroke}\" stroke-width=\"${sw}\" />` +\n // Shoulders\n `\\n <path d=\"M5.62842 18.3563C7.08963 17.0398 9.39997 16 12 16C14.6 16 16.9104 17.0398 18.3716 18.3563\" fill=\"none\" stroke=\"${iconStroke}\" stroke-width=\"${sw}\" />` +\n `\\n </g>`,\n )\n // Label below the icon (supports multi-line)\n parts.push(\n ' ' +\n renderMultilineText(\n label,\n x,\n y + height + 14,\n fontSizes.nodeLabel,\n `font-size=\"${fontSizes.nodeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.nodeLabel}\" fill=\"var(--_text)\"`,\n ),\n )\n } else {\n // Participant: rectangle box with label (supports multi-line)\n const boxX = x - width / 2\n parts.push(\n ` <rect x=\"${boxX}\" y=\"${y}\" width=\"${width}\" height=\"${height}\" rx=\"4\" ry=\"4\" ` +\n `fill=\"var(--_node-fill)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n parts.push(\n ' ' +\n renderMultilineText(\n label,\n x,\n y + height / 2,\n fontSizes.nodeLabel,\n `font-size=\"${fontSizes.nodeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.nodeLabel}\" fill=\"var(--_text)\"`,\n ),\n )\n }\n\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n/**\n * Render a lifeline (dashed vertical line from actor to bottom).\n * Includes data-actor to link to its actor.\n */\nfunction renderLifeline(lifeline: Lifeline): string {\n const line =\n `<line class=\"lifeline\" data-actor=\"${escapeAttr(lifeline.actorId)}\" ` +\n `x1=\"${lifeline.x}\" y1=\"${lifeline.topY}\" x2=\"${lifeline.x}\" y2=\"${lifeline.bottomY}\" ` +\n `stroke=\"var(--_line)\" stroke-width=\"0.75\" stroke-dasharray=\"6 4\" />`\n if (!lifeline.destroyed) return line\n // `destroy X`: the lifeline ends at the destroying message's row, marked\n // with a cross centred on it — the same glyph Mermaid uses. Drawn in the\n // lifeline pass (before messages), so the destroying arrow lands on top.\n const { x, bottomY: y } = lifeline\n const r = DESTROY_CROSS_HALF\n return (\n line +\n `\\n<path class=\"destroy\" data-actor=\"${escapeAttr(lifeline.actorId)}\" ` +\n `d=\"M${x - r} ${y - r} L${x + r} ${y + r} M${x + r} ${y - r} L${x - r} ${y + r}\" ` +\n `fill=\"none\" stroke=\"var(--_line)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" stroke-linecap=\"round\" />`\n )\n}\n\n/** Half-size of the `destroy` cross at the end of a lifeline, in px. */\nconst DESTROY_CROSS_HALF = 8\n\n/**\n * Render an activation box (narrow filled rectangle on lifeline).\n * Includes data-actor to link to its actor.\n */\nfunction renderActivation(activation: Activation): string {\n return (\n `<rect class=\"activation\" data-actor=\"${escapeAttr(activation.actorId)}\" ` +\n `x=\"${activation.x}\" y=\"${activation.topY}\" width=\"${activation.width}\" height=\"${activation.bottomY - activation.topY}\" ` +\n `fill=\"var(--_node-fill)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />`\n )\n}\n\n/**\n * Render a message arrow with label.\n * Wrapped in <g class=\"message\"> with semantic data attributes.\n */\nfunction renderMessage(msg: PositionedMessage, fontSizes: FontSizes): string {\n const parts: string[] = []\n const dashArray = msg.lineStyle === 'dashed' ? ' stroke-dasharray=\"6 4\"' : ''\n const markerId = msg.arrowHead === 'filled' ? 'seq-arrow' : 'seq-arrow-open'\n // Bidirectional arrows (`<<->>` / `<<-->>`) reuse the same marker on both\n // ends — the marker defs use orient=\"auto-start-reverse\", which SVG\n // automatically flips 180° for marker-start so it points outward at the\n // line's start instead of reusing the marker-end orientation.\n const markerStart = msg.bidirectional\n ? ` marker-start=\"url(#${markerId})\"`\n : ''\n\n // Semantic wrapper with message metadata\n parts.push(\n `<g class=\"message\" data-from=\"${escapeAttr(msg.from)}\" data-to=\"${escapeAttr(msg.to)}\" ` +\n `data-label=\"${escapeAttr(msg.label)}\" data-line-style=\"${msg.lineStyle}\" ` +\n `data-arrow-head=\"${msg.arrowHead}\" data-self=\"${msg.isSelf}\" data-bidirectional=\"${msg.bidirectional}\">`,\n )\n\n if (msg.isSelf) {\n // Self-message: curved loop going right and back\n // Loop dimensions - loopH is fixed, loopW provides minimum clearance\n const loopW = 30\n const loopH = 20\n const labelPadding = 8 // Space between loop and label\n parts.push(\n ` <polyline points=\"${msg.x1},${msg.y} ${msg.x1 + loopW},${msg.y} ${msg.x1 + loopW},${msg.y + loopH} ${msg.x2},${msg.y + loopH}\" ` +\n `fill=\"none\" stroke=\"var(--_line)\" stroke-width=\"${STROKE_WIDTHS.connector}\"${dashArray} marker-end=\"url(#${markerId})\"${markerStart} />`,\n )\n // Label to the right of the loop (supports multi-line)\n parts.push(\n ' ' +\n renderMultilineText(\n msg.label,\n msg.x1 + loopW + labelPadding,\n msg.y + loopH / 2,\n fontSizes.edgeLabel,\n `font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"start\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n } else {\n // Normal message: horizontal arrow\n parts.push(\n ` <line x1=\"${msg.x1}\" y1=\"${msg.y}\" x2=\"${msg.x2}\" y2=\"${msg.y}\" ` +\n `stroke=\"var(--_line)\" stroke-width=\"${STROKE_WIDTHS.connector}\"${dashArray} marker-end=\"url(#${markerId})\"${markerStart} />`,\n )\n // Label above the arrow, centered (supports multi-line)\n const midX = (msg.x1 + msg.x2) / 2\n parts.push(\n ' ' +\n renderMultilineText(\n msg.label,\n midX,\n msg.y - 10,\n fontSizes.edgeLabel,\n `font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n }\n\n if (msg.seqNumber !== undefined) {\n parts.push(' ' + renderSeqNumberBadge(msg, fontSizes))\n }\n\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n/**\n * Render the small `autonumber` badge Mermaid draws near the start of a\n * message arrow: a circle with the sequence number centered inside it,\n * layered on top of the arrow rather than folded into the label text.\n */\nfunction renderSeqNumberBadge(\n msg: PositionedMessage,\n fontSizes: FontSizes,\n): string {\n const radius = 8\n const fontSize = Math.max(fontSizes.edgeLabel - 2, 8)\n // Bidirectional messages also draw an arrowhead at x1 (the departure\n // end) — badge-at-x1 would sit on top of it. Shift the badge into the\n // arrow span, clear of the marker, in that case. One-way messages have\n // no marker at x1, so they keep the badge exactly at the departure\n // point as before.\n const cx = msg.bidirectional\n ? msg.x1 + Math.sign(msg.x2 - msg.x1) * (radius + ARROW_HEAD.width)\n : msg.x1\n return (\n `<g class=\"seq-number\">` +\n `<circle cx=\"${cx}\" cy=\"${msg.y}\" r=\"${radius}\" fill=\"var(--bg)\" stroke=\"var(--_arrow)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />` +\n renderMultilineText(\n String(msg.seqNumber),\n cx,\n msg.y,\n fontSize,\n `font-size=\"${fontSize}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_arrow)\"`,\n ) +\n `</g>`\n )\n}\n\n/**\n * How much of a `box` colour shows through: the colour is mixed into the\n * theme background at this percentage rather than painted as-is, so\n * `box Purple` reads as a purple tint on a light theme and a deep purple on\n * a dark one instead of the same opaque swatch on both (Mermaid paints the\n * literal colour). The stroke and label use theme variables, so a colourless\n * box still shows its grouping in every theme.\n */\nconst BOX_COLOR_MIX_PERCENT = 35\n\n/**\n * Render a `box … end` participant-group background: a full-height rect\n * behind the grouped lifelines with the label centred in its top band.\n * Wrapped in <g class=\"participant-box\"> with semantic data attributes.\n *\n * `box.color` is emitted verbatim inside `color-mix()` — safe because the\n * parser only records a value that matched its fixed named-colour list or\n * the hex / rgb() / hsl() patterns (see box-color.ts), never arbitrary\n * diagram text.\n */\nfunction renderParticipantBox(\n box: PositionedParticipantBox,\n fontSizes: FontSizes,\n): string {\n const labelAttr = box.label ? ` data-label=\"${escapeAttr(box.label)}\"` : ''\n const colorAttr =\n box.color !== undefined ? ` data-color=\"${escapeAttr(box.color)}\"` : ''\n const fill =\n box.color !== undefined\n ? `color-mix(in srgb, ${escapeAttr(box.color)} ${BOX_COLOR_MIX_PERCENT}%, var(--bg))`\n : 'none'\n const parts: string[] = [\n `<g class=\"participant-box\"${labelAttr}${colorAttr}>`,\n ` <rect x=\"${box.x}\" y=\"${box.y}\" width=\"${box.width}\" height=\"${box.height}\" rx=\"4\" ry=\"4\" ` +\n `fill=\"${fill}\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />`,\n ]\n if (box.label) {\n // Centred in the label band the layout reserved above the actor boxes.\n parts.push(\n ' ' +\n renderMultilineText(\n box.label,\n box.x + box.width / 2,\n box.y + boxLabelHeight(fontSizes.edgeLabel) / 2,\n fontSizes.edgeLabel,\n `font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.groupHeader}\" fill=\"var(--_text-sec)\"`,\n ),\n )\n }\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n/**\n * Render a block background (loop/alt/opt).\n * Wrapped in <g class=\"block\"> with semantic data attributes.\n */\nfunction renderBlock(block: PositionedBlock, fontSizes: FontSizes): string {\n const parts: string[] = []\n\n // Semantic wrapper with block metadata\n const labelAttr = block.label\n ? ` data-label=\"${escapeAttr(block.label)}\"`\n : ''\n parts.push(\n `<g class=\"block\" data-type=\"${escapeAttr(block.type)}\"${labelAttr}>`,\n )\n\n // Outer rectangle\n parts.push(\n ` <rect x=\"${block.x}\" y=\"${block.y}\" width=\"${block.width}\" height=\"${block.height}\" ` +\n `rx=\"0\" ry=\"0\" fill=\"none\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n\n // Type label tab (top-left corner)\n // For multi-line block labels, we use the first line for the tab but show full label\n const labelText = `${block.type}${block.label ? ` [${block.label}]` : ''}`\n // Audited for issue #100: `String.prototype.split` always returns an\n // array with at least one element (even splitting `''` yields `['']`),\n // so index 0 is guaranteed to exist regardless of whether `labelText`\n // contains a newline. `noUncheckedIndexedAccess` can't see that\n // language-level guarantee; left as-is, no behavior change.\n const firstLine = labelText.split('\\n')[0]!\n const tabWidth =\n estimateTextWidth(\n firstLine,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.groupHeader,\n ) + 16\n const tabHeight = 18\n\n parts.push(\n ` <rect x=\"${block.x}\" y=\"${block.y}\" width=\"${tabWidth}\" height=\"${tabHeight}\" ` +\n `fill=\"var(--_group-hdr)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n // Block type label (supports multi-line via <br> tags)\n parts.push(\n ' ' +\n renderMultilineText(\n labelText,\n block.x + 6,\n block.y + tabHeight / 2,\n fontSizes.edgeLabel,\n `font-size=\"${fontSizes.edgeLabel}\" font-weight=\"${FONT_WEIGHTS.groupHeader}\" fill=\"var(--_text-sec)\"`,\n ),\n )\n\n // Divider lines (for alt/else, par/and)\n for (const divider of block.dividers) {\n parts.push(\n ` <line x1=\"${block.x}\" y1=\"${divider.y}\" x2=\"${block.x + block.width}\" y2=\"${divider.y}\" ` +\n `stroke=\"var(--_line)\" stroke-width=\"0.75\" stroke-dasharray=\"6 4\" />`,\n )\n if (divider.label) {\n // Divider label supports multi-line\n parts.push(\n ' ' +\n renderMultilineText(\n `[${divider.label}]`,\n block.x + 8,\n divider.y + 14,\n fontSizes.edgeLabel,\n `font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"start\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n }\n }\n\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n/**\n * Render a note box.\n * Wrapped in <g class=\"note\"> with semantic data attributes.\n */\nfunction renderNote(note: PositionedNote, fontSizes: FontSizes): string {\n // Dog-ear note: polygon with clipped top-right corner + fold triangle\n const foldSize = 6\n const { x, y, width: w, height: h } = note\n\n // Build actor reference attribute if present\n const actorsAttr =\n note.actors && note.actors.length > 0\n ? ` data-actors=\"${note.actors.map(escapeAttr).join(',')}\"`\n : ''\n const positionAttr = note.position\n ? ` data-position=\"${escapeAttr(note.position)}\"`\n : ''\n\n // Note body: polygon with top-right corner cut off\n // (x,y) → (x+w-fold,y) → (x+w,y+fold) → (x+w,y+h) → (x,y+h)\n const bodyPoints = [\n `${x},${y}`,\n `${x + w - foldSize},${y}`,\n `${x + w},${y + foldSize}`,\n `${x + w},${y + h}`,\n `${x},${y + h}`,\n ].join(' ')\n\n return (\n `<g class=\"note\"${positionAttr}${actorsAttr}>` +\n // Note body with bg fill and clipped corner\n `\\n <polygon points=\"${bodyPoints}\" ` +\n `fill=\"var(--bg)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />` +\n // Fold triangle (the folded-over corner)\n `\\n <polygon points=\"${x + w - foldSize},${y} ${x + w},${y + foldSize} ${x + w - foldSize},${y + foldSize}\" ` +\n `fill=\"var(--_inner-stroke)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />` +\n // Note text (supports multi-line)\n `\\n ${renderMultilineText(\n note.text,\n x + w / 2,\n y + h / 2,\n fontSizes.edgeLabel,\n `font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n )}` +\n `\\n</g>`\n )\n}\n","import type {\n XYChart,\n PositionedXYChart,\n PositionedAxis,\n AxisTick,\n PositionedBar,\n PositionedLine,\n GridLine,\n PlotArea,\n LegendItem,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { XyChartRenderOptions } from '@zombie-mermaid/core'\nimport { estimateTextWidth } from '../styles.ts'\n\n// ============================================================================\n// XY Chart layout engine\n//\n// Computes pixel coordinates for all chart elements. No dagre needed —\n// direct coordinate-space mapping for axes, bars, lines, and grid.\n// ============================================================================\n\n/** Layout constants — aligned with Chart.js default proportions */\nconst XY = {\n plotWidth: 600,\n plotHeight: 340,\n padding: 22,\n titleFontSize: 18,\n titleFontWeight: 600,\n titleHeight: 42,\n axisLabelFontSize: 14,\n axisLabelFontWeight: 400,\n axisTitleFontSize: 15,\n axisTitleFontWeight: 500,\n xLabelHeight: 38,\n yLabelWidth: 58,\n yLabelGap: 18,\n axisTitlePad: 30,\n tickLength: 4,\n barPadRatio: 0.2,\n barGroupGap: 0,\n maxBarWidth: 40,\n legendFontSize: 14,\n legendFontWeight: 400,\n legendHeight: 28,\n legendSwatchW: 14,\n legendSwatchH: 14,\n legendGap: 6,\n legendItemGap: 16,\n} as const\n\n/**\n * Lay out a parsed XY chart by computing pixel coordinates.\n */\nexport function layoutXYChart(\n chart: XYChart,\n _options: XyChartRenderOptions = {},\n): PositionedXYChart {\n if (chart.horizontal) return layoutHorizontal(chart)\n return layoutVertical(chart)\n}\n\n// ============================================================================\n// Vertical layout (default)\n// ============================================================================\n\nfunction layoutVertical(chart: XYChart): PositionedXYChart {\n const hasTitle = !!chart.title\n const hasXTitle = !!chart.xAxis.title\n const hasYTitle = !!chart.yAxis.title\n const hasLegend = chart.series.length > 1\n\n // Compute y-axis label width from tick labels\n //\n // parseXYChart always derives a y-axis range (from data or a 0-100\n // fallback — see xychart/parser.ts) before returning, so this is never\n // actually undefined for parsed input. That guarantee lives in a\n // different module, so narrow it here explicitly instead of trusting it\n // silently across the boundary.\n const yRange = chart.yAxis.range\n if (!yRange) {\n /* v8 ignore next */\n throw new Error('XY chart: y-axis range was not set by the parser')\n }\n const yTicks = niceTickValues(yRange.min, yRange.max)\n const maxYLabelWidth = Math.max(\n ...yTicks.map((v) =>\n estimateTextWidth(\n formatTickValue(v),\n XY.axisLabelFontSize,\n XY.axisLabelFontWeight,\n ),\n ),\n XY.yLabelWidth,\n )\n\n // Margins\n const top =\n XY.padding +\n (hasTitle ? XY.titleHeight : 0) +\n (hasLegend ? XY.legendHeight : 0)\n const bottom =\n XY.padding + XY.xLabelHeight + (hasXTitle ? XY.axisTitlePad : 0)\n const left =\n XY.padding +\n maxYLabelWidth +\n XY.yLabelGap +\n (hasYTitle ? XY.axisTitlePad : 0)\n const right = XY.padding\n\n const plotW = XY.plotWidth\n const plotH = XY.plotHeight\n const totalW = left + plotW + right\n const totalH = top + plotH + bottom\n\n const plotArea: PlotArea = { x: left, y: top, width: plotW, height: plotH }\n\n // Scales\n const dataCount = getDataCount(chart)\n const xScale = (i: number) => left + (i + 0.5) * (plotW / dataCount)\n const bandWidth = plotW / dataCount\n const yScale = (v: number) => {\n const t = (v - yRange.min) / (yRange.max - yRange.min || 1)\n return top + plotH - t * plotH\n }\n\n // X-axis ticks\n const xTicks = buildXTicks(chart, xScale, top + plotH, bandWidth)\n\n // Y-axis ticks\n const yAxisTicks: AxisTick[] = yTicks.map((v) => ({\n label: formatTickValue(v),\n x: left,\n y: yScale(v),\n tx: left - XY.tickLength,\n ty: yScale(v),\n labelX: left - XY.yLabelGap,\n labelY: yScale(v),\n textAnchor: 'end' as const,\n }))\n\n // Grid lines (horizontal at each y tick)\n const gridLines: GridLine[] = yTicks.map((v) => ({\n x1: left,\n y1: yScale(v),\n x2: left + plotW,\n y2: yScale(v),\n }))\n\n // Category labels for data attributes\n const catLabels = getCategoryLabels(chart, dataCount)\n\n // Global color index map: each series gets a unique color index regardless of type\n const colorMap = chart.series.map((_, i) => i)\n\n // Bars\n const bars = layoutBars(\n chart,\n xScale,\n yScale,\n bandWidth,\n yRange.min,\n catLabels,\n colorMap,\n )\n\n // Lines\n const lines = layoutLines(chart, xScale, yScale, catLabels, colorMap)\n\n // Legend\n const legendY =\n XY.padding + (hasTitle ? XY.titleHeight : 0) + XY.legendHeight / 2\n const legend = hasLegend\n ? buildLegendItems(chart, totalW / 2, legendY, colorMap)\n : []\n\n // Axis lines\n const xAxisLine = {\n x1: left,\n y1: top + plotH,\n x2: left + plotW,\n y2: top + plotH,\n }\n const yAxisLine = { x1: left, y1: top, x2: left, y2: top + plotH }\n\n // Axis titles\n const xAxisObj: PositionedAxis = {\n ticks: xTicks,\n line: xAxisLine,\n ...(chart.xAxis.title\n ? {\n title: {\n text: chart.xAxis.title,\n x: left + plotW / 2,\n y: totalH - XY.padding,\n },\n }\n : {}),\n }\n const yAxisObj: PositionedAxis = {\n ticks: yAxisTicks,\n line: yAxisLine,\n ...(chart.yAxis.title\n ? {\n title: {\n text: chart.yAxis.title,\n x: XY.padding + 4,\n y: top + plotH / 2,\n rotate: -90,\n },\n }\n : {}),\n }\n\n // Title\n const titleObj = chart.title\n ? { text: chart.title, x: totalW / 2, y: XY.padding + XY.titleFontSize }\n : undefined\n\n return {\n width: totalW,\n height: totalH,\n title: titleObj,\n xAxis: xAxisObj,\n yAxis: yAxisObj,\n plotArea,\n bars,\n lines,\n gridLines,\n legend,\n }\n}\n\n// ============================================================================\n// Horizontal layout\n// ============================================================================\n\nfunction layoutHorizontal(chart: XYChart): PositionedXYChart {\n const hasTitle = !!chart.title\n const hasXTitle = !!chart.xAxis.title\n const hasYTitle = !!chart.yAxis.title\n const hasLegend = chart.series.length > 1\n\n // In horizontal mode: categories go on y-axis (left side), values go on x-axis (bottom)\n //\n // See the matching comment in layoutVertical: the parser always derives a\n // y-axis range before returning, but that guarantee lives in a different\n // module, so it's narrowed here explicitly rather than trusted silently.\n const yRange = chart.yAxis.range\n if (!yRange) {\n /* v8 ignore next */\n throw new Error('XY chart: y-axis range was not set by the parser')\n }\n const valueTicks = niceTickValues(yRange.min, yRange.max)\n\n // Compute category label widths for left margin\n const dataCount = getDataCount(chart)\n const catLabels = getCategoryLabels(chart, dataCount)\n const maxCatLabelWidth = Math.max(\n ...catLabels.map((l) =>\n estimateTextWidth(l, XY.axisLabelFontSize, XY.axisLabelFontWeight),\n ),\n 40,\n )\n\n const top =\n XY.padding +\n (hasTitle ? XY.titleHeight : 0) +\n (hasLegend ? XY.legendHeight : 0)\n const bottom =\n XY.padding + XY.xLabelHeight + (hasYTitle ? XY.axisTitlePad : 0)\n const left =\n XY.padding +\n maxCatLabelWidth +\n XY.yLabelGap +\n (hasXTitle ? XY.axisTitlePad : 0)\n const right = XY.padding\n\n const plotW = XY.plotWidth\n const plotH = XY.plotHeight\n const totalW = left + plotW + right\n const totalH = top + plotH + bottom\n\n const plotArea: PlotArea = { x: left, y: top, width: plotW, height: plotH }\n\n // Value scale (horizontal: left to right)\n const valueScale = (v: number) => {\n const t = (v - yRange.min) / (yRange.max - yRange.min || 1)\n return left + t * plotW\n }\n\n // Category scale (vertical: top to bottom)\n const bandHeight = plotH / dataCount\n const catScale = (i: number) => top + (i + 0.5) * bandHeight\n\n // X-axis (bottom): value ticks\n const xTicks: AxisTick[] = valueTicks.map((v) => ({\n label: formatTickValue(v),\n x: valueScale(v),\n y: top + plotH,\n tx: valueScale(v),\n ty: top + plotH + XY.tickLength,\n labelX: valueScale(v),\n labelY: top + plotH + 18,\n textAnchor: 'middle' as const,\n }))\n\n // Y-axis (left): category ticks\n const yTicks: AxisTick[] = catLabels.map((label, i) => ({\n label,\n x: left,\n y: catScale(i),\n tx: left - XY.tickLength,\n ty: catScale(i),\n labelX: left - XY.yLabelGap,\n labelY: catScale(i),\n textAnchor: 'end' as const,\n }))\n\n // Grid lines (vertical at each value tick)\n const gridLines: GridLine[] = valueTicks.map((v) => ({\n x1: valueScale(v),\n y1: top,\n x2: valueScale(v),\n y2: top + plotH,\n }))\n\n // Global color index map\n const colorMap = chart.series.map((_, i) => i)\n\n // Bars (horizontal)\n const barSeries = chart.series.filter((s) => s.type === 'bar')\n const barCount = barSeries.length\n const bars: PositionedBar[] = []\n if (barCount > 0) {\n const usable = bandHeight * (1 - XY.barPadRatio)\n const rawBarH =\n barCount > 1\n ? (usable - (barCount - 1) * XY.barGroupGap) / barCount\n : usable\n const singleBarH = Math.min(rawBarH, XY.maxBarWidth)\n const groupH =\n barCount > 1\n ? singleBarH * barCount + XY.barGroupGap * (barCount - 1)\n : singleBarH\n let bIdx = 0\n let seriesArrayIdx = 0\n for (const s of chart.series) {\n if (s.type !== 'bar') {\n seriesArrayIdx++\n continue\n }\n for (let i = 0; i < s.data.length; i++) {\n const cy = catScale(i)\n const groupTop = cy - groupH / 2\n const by = groupTop + bIdx * (singleBarH + XY.barGroupGap)\n const valX = valueScale(Math.max(s.data[i]!, yRange.min))\n const baseX = valueScale(Math.max(0, yRange.min))\n bars.push({\n x: Math.min(baseX, valX),\n y: by,\n width: Math.abs(valX - baseX),\n height: singleBarH,\n value: s.data[i]!,\n // A series can carry more data points than the chart has category\n // labels for (e.g. a bar/line series longer than x-axis.categories\n // or the series that determined dataCount), so catLabels[i] is not\n // guaranteed to exist just because s.data[i] does. `label` is\n // already optional on PositionedBar, so let a missing entry flow\n // through as undefined instead of asserting past a real gap.\n label: catLabels[i],\n seriesIndex: bIdx,\n colorIndex: colorMap[seriesArrayIdx]!,\n })\n }\n bIdx++\n seriesArrayIdx++\n }\n }\n\n // Lines (horizontal: value on x, category index on y)\n const lines: PositionedLine[] = []\n let lineIdx = 0\n let lineSeriesIdx = 0\n for (const s of chart.series) {\n if (s.type !== 'line') {\n lineSeriesIdx++\n continue\n }\n const points = s.data.map((v, i) => ({\n x: valueScale(v),\n y: catScale(i),\n value: v,\n // See the matching comment in the bars loop above: a line series can\n // outrun catLabels, and the point's `label` is already optional.\n label: catLabels[i],\n }))\n lines.push({\n points,\n seriesIndex: lineIdx,\n colorIndex: colorMap[lineSeriesIdx]!,\n })\n lineIdx++\n lineSeriesIdx++\n }\n\n const xAxisLine = {\n x1: left,\n y1: top + plotH,\n x2: left + plotW,\n y2: top + plotH,\n }\n const yAxisLine = { x1: left, y1: top, x2: left, y2: top + plotH }\n\n // In horizontal mode, the \"y-axis\" title describes values (bottom) and \"x-axis\" title describes categories (left)\n const xAxisObj: PositionedAxis = {\n ticks: xTicks,\n line: xAxisLine,\n ...(chart.yAxis.title\n ? {\n title: {\n text: chart.yAxis.title,\n x: left + plotW / 2,\n y: totalH - XY.padding,\n },\n }\n : {}),\n }\n const yAxisObj: PositionedAxis = {\n ticks: yTicks,\n line: yAxisLine,\n ...(chart.xAxis.title\n ? {\n title: {\n text: chart.xAxis.title,\n x: XY.padding + 4,\n y: top + plotH / 2,\n rotate: -90,\n },\n }\n : {}),\n }\n\n const titleObj = chart.title\n ? { text: chart.title, x: totalW / 2, y: XY.padding + XY.titleFontSize }\n : undefined\n\n // Legend\n const legendY =\n XY.padding + (hasTitle ? XY.titleHeight : 0) + XY.legendHeight / 2\n const legend = hasLegend\n ? buildLegendItems(chart, totalW / 2, legendY, colorMap)\n : []\n\n return {\n width: totalW,\n height: totalH,\n horizontal: true,\n title: titleObj,\n xAxis: xAxisObj,\n yAxis: yAxisObj,\n plotArea,\n bars,\n lines,\n gridLines,\n legend,\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction getDataCount(chart: XYChart): number {\n if (chart.xAxis.categories) return chart.xAxis.categories.length\n // For numeric range, use the length of the first series\n for (const s of chart.series) {\n if (s.data.length > 0) return s.data.length\n }\n return 1\n}\n\nfunction getCategoryLabels(chart: XYChart, count: number): string[] {\n if (chart.xAxis.categories) return chart.xAxis.categories\n if (chart.xAxis.range) {\n const { min, max } = chart.xAxis.range\n const step = count > 1 ? (max - min) / (count - 1) : 0\n return Array.from({ length: count }, (_, i) =>\n formatTickValue(min + step * i),\n )\n }\n return Array.from({ length: count }, (_, i) => String(i + 1))\n}\n\nfunction buildXTicks(\n chart: XYChart,\n xScale: (i: number) => number,\n axisY: number,\n _bandWidth: number,\n): AxisTick[] {\n const count = getDataCount(chart)\n const labels = getCategoryLabels(chart, count)\n return labels.map((label, i) => ({\n label,\n x: xScale(i),\n y: axisY,\n tx: xScale(i),\n ty: axisY + XY.tickLength,\n labelX: xScale(i),\n labelY: axisY + 18,\n textAnchor: 'middle' as const,\n }))\n}\n\nfunction layoutBars(\n chart: XYChart,\n xScale: (i: number) => number,\n yScale: (v: number) => number,\n bandWidth: number,\n yMin: number,\n catLabels: string[],\n colorMap: number[],\n): PositionedBar[] {\n const barSeries = chart.series.filter((s) => s.type === 'bar')\n const barCount = barSeries.length\n if (barCount === 0) return []\n\n const usable = bandWidth * (1 - XY.barPadRatio)\n const rawBarW =\n barCount > 1\n ? (usable - (barCount - 1) * XY.barGroupGap) / barCount\n : usable\n const singleBarW = Math.min(rawBarW, XY.maxBarWidth)\n const groupW =\n barCount > 1\n ? singleBarW * barCount + XY.barGroupGap * (barCount - 1)\n : singleBarW\n const bars: PositionedBar[] = []\n\n let bIdx = 0\n let seriesArrayIdx = 0\n for (const s of chart.series) {\n if (s.type !== 'bar') {\n seriesArrayIdx++\n continue\n }\n for (let i = 0; i < s.data.length; i++) {\n const cx = xScale(i)\n const groupLeft = cx - groupW / 2\n const bx = groupLeft + bIdx * (singleBarW + XY.barGroupGap)\n const valY = yScale(s.data[i]!)\n const baseY = yScale(Math.max(0, yMin))\n bars.push({\n x: bx,\n y: Math.min(valY, baseY),\n width: singleBarW,\n height: Math.abs(baseY - valY),\n value: s.data[i]!,\n // catLabels has one entry per dataCount, which is not necessarily\n // s.data.length for every series. `label` is already optional on\n // PositionedBar, so let a missing entry flow through as undefined\n // instead of asserting past a real (if unusual) length mismatch.\n // See layoutHorizontal's matching comment.\n label: catLabels[i],\n seriesIndex: bIdx,\n colorIndex: colorMap[seriesArrayIdx]!,\n })\n }\n bIdx++\n seriesArrayIdx++\n }\n return bars\n}\n\nfunction layoutLines(\n chart: XYChart,\n xScale: (i: number) => number,\n yScale: (v: number) => number,\n catLabels: string[],\n colorMap: number[],\n): PositionedLine[] {\n const lines: PositionedLine[] = []\n let lineIdx = 0\n let seriesArrayIdx = 0\n for (const s of chart.series) {\n if (s.type !== 'line') {\n seriesArrayIdx++\n continue\n }\n const points = s.data.map((v, i) => ({\n x: xScale(i),\n y: yScale(v),\n value: v,\n // See layoutBars' matching comment: a line series can outrun\n // catLabels, and the point's `label` is already optional.\n label: catLabels[i],\n }))\n lines.push({\n points,\n seriesIndex: lineIdx,\n colorIndex: colorMap[seriesArrayIdx]!,\n })\n lineIdx++\n seriesArrayIdx++\n }\n return lines\n}\n\n/** Generate \"nice\" tick values for a numeric range */\nfunction niceTickValues(min: number, max: number): number[] {\n const range = max - min\n if (range <= 0) return [min]\n\n // Find nice interval\n const rawInterval = range / 6\n const magnitude = Math.pow(10, Math.floor(Math.log10(rawInterval)))\n const residual = rawInterval / magnitude\n let niceInterval: number\n if (residual <= 1.5) niceInterval = magnitude\n else if (residual <= 3) niceInterval = 2 * magnitude\n else if (residual <= 7) niceInterval = 5 * magnitude\n else niceInterval = 10 * magnitude\n\n const start = Math.ceil(min / niceInterval) * niceInterval\n const ticks: number[] = []\n for (let v = start; v <= max + niceInterval * 0.001; v += niceInterval) {\n ticks.push(Math.round(v * 1e10) / 1e10) // avoid floating-point noise\n }\n return ticks\n}\n\nfunction formatTickValue(v: number): string {\n if (Number.isInteger(v)) return String(v)\n // Limit decimal places\n return v.toFixed(Math.abs(v) < 10 ? 1 : 0)\n}\n\n/** Build centered legend items for multi-series charts */\nfunction buildLegendItems(\n chart: XYChart,\n centerX: number,\n y: number,\n colorMap: number[],\n): LegendItem[] {\n const items: LegendItem[] = []\n let barIdx = 0,\n lineIdx = 0\n for (let si = 0; si < chart.series.length; si++) {\n const s = chart.series[si]!\n const label = s.type === 'bar' ? `Bar ${barIdx + 1}` : `Line ${lineIdx + 1}`\n items.push({\n label,\n x: 0,\n y,\n type: s.type,\n seriesIndex: s.type === 'bar' ? barIdx : lineIdx,\n colorIndex: colorMap[si]!,\n })\n if (s.type === 'bar') barIdx++\n else lineIdx++\n }\n\n // Measure total width, then center\n const itemWidths = items.map((item) => {\n const textW = estimateTextWidth(\n item.label,\n XY.legendFontSize,\n XY.legendFontWeight,\n )\n return XY.legendSwatchW + XY.legendGap + textW\n })\n const totalWidth =\n itemWidths.reduce((a, b) => a + b, 0) +\n (items.length - 1) * XY.legendItemGap\n let x = centerX - totalWidth / 2\n\n for (let i = 0; i < items.length; i++) {\n items[i]!.x = x\n x += itemWidths[i]! + XY.legendItemGap\n }\n\n return items\n}\n","import type { PositionedXYChart } from '@zombie-mermaid/mermaid-parser'\nimport type { DiagramColors, SvgEmitOptions } from '@zombie-mermaid/core'\nimport { svgOpenTag, buildStyleBlock, styleOpenTag } from '@zombie-mermaid/core'\nimport { withDataSrc } from '../renderer.ts'\nimport { TEXT_BASELINE_SHIFT, estimateTextWidth } from '../styles.ts'\nimport {\n getSeriesColor,\n CHART_ACCENT_FALLBACK,\n} from '@zombie-mermaid/mermaid-parser'\n\n// ============================================================================\n// XY Chart SVG renderer\n//\n// Renders positioned XY charts to SVG strings.\n// All colors use CSS custom properties (var(--_xxx)) from the theme system.\n//\n// Visual style: clean, minimal, modern. Inspired by Apple/Craft chart design.\n// - No axis lines or tick marks — labels float freely\n// - Ultra-subtle solid grid lines\n// - Bars with rounded tops, flat at baseline\n// - Smooth curved lines, no visible dots (dots appear on hover)\n//\n// Render order (back to front):\n// 1. Grid lines\n// 2. Bars (as paths with rounded tops)\n// 3. Lines (smooth curves)\n// 4. Dots (hidden by default, visible on hover when interactive)\n// 5. Axis labels\n// 6. Axis titles\n// 7. Chart title\n// 8. Legend\n// ============================================================================\n\nconst CHART_FONT = {\n titleSize: 18,\n titleWeight: 600,\n axisTitleSize: 15,\n axisTitleWeight: 500,\n labelSize: 14,\n labelWeight: 400,\n legendSize: 14,\n legendWeight: 400,\n dotRadius: 5,\n lineWidth: 2.5,\n barRadius: 8,\n} as const\n\nconst TIP = {\n fontSize: 15,\n fontWeight: 500,\n height: 32,\n padX: 14,\n offsetY: 12,\n rx: 8,\n minY: 4,\n pointerSize: 6,\n} as const\n\n/**\n * Render a positioned XY chart as an SVG string.\n *\n * @param embedSource - Original diagram source to stamp onto the root\n * `<svg>` as `data-src` (from `options.embedSource`).\n * Omitted when the option is off.\n * @param title - Accessible name (from `options.title`). See svgOpenTag() in\n * packages/core/src/theme.ts.\n * @param decorative - Marks the SVG decorative (from `options.decorative`).\n * @param emit - Strict-CSP controls (from `options.nonce` /\n * `options.styleAttribute`, see #216). Default: no nonce,\n * root `style` attribute on.\n */\nexport function renderXYChartSvg(\n chart: PositionedXYChart,\n colors: DiagramColors,\n font: string = 'Inter',\n transparent: boolean = false,\n interactive: boolean = false,\n embedSource?: string,\n title?: string,\n decorative?: boolean,\n emit: SvgEmitOptions = {},\n): string {\n const parts: string[] = []\n\n // SVG root + base styles\n // Stamp data-xychart-colors so theme-switching JS knows how many series color vars to update\n const maxColorIdx = Math.max(\n 0,\n ...chart.bars.map((b) => b.colorIndex),\n ...chart.lines.map((l) => l.colorIndex),\n )\n const svgTag = withDataSrc(\n svgOpenTag(\n chart.width,\n chart.height,\n colors,\n transparent,\n title,\n decorative,\n undefined,\n emit.styleAttribute,\n ).replace('<svg ', `<svg data-xychart-colors=\"${maxColorIdx}\" `),\n embedSource,\n )\n parts.push(svgTag)\n parts.push(buildStyleBlock(font, false, emit.nonce))\n\n // Sparse lines (≤12 points) show dots by default\n const maxLinePoints = Math.max(...chart.lines.map((l) => l.points.length), 0)\n const sparse = maxLinePoints > 0 && maxLinePoints <= 12\n\n // Chart-specific styles + gradient defs\n const { style: chartCss, defs: chartDefs } = chartStyles(\n chart,\n interactive,\n sparse,\n colors.accent,\n colors.bg,\n emit.nonce,\n )\n parts.push(chartCss)\n if (chartDefs) parts.push(chartDefs)\n\n // 1. Dot grid (dense dots across plot area, aligned to tick spacing)\n const { plotArea } = chart\n const xTicks = chart.xAxis.ticks.map((t) => t.x)\n const yVals = chart.horizontal\n ? chart.yAxis.ticks.map((t) => t.y)\n : chart.gridLines.map((g) => g.y1)\n const xBase =\n xTicks.length > 1 ? Math.abs(xTicks[1]! - xTicks[0]!) : plotArea.width / 6\n const yBase =\n yVals.length > 1 ? Math.abs(yVals[1]! - yVals[0]!) : plotArea.height / 6\n const xGap = xBase / Math.max(1, Math.round(xBase / 20))\n const yGap = yBase / Math.max(1, Math.round(yBase / 20))\n const xAnchor = xTicks[0] ?? plotArea.x\n const yAnchor = yVals[0] ?? plotArea.y\n const xStart = xAnchor - Math.ceil((xAnchor - plotArea.x) / xGap) * xGap\n const yStart = yAnchor - Math.ceil((yAnchor - plotArea.y) / yGap) * yGap\n for (let y = yStart; y <= plotArea.y + plotArea.height + 0.5; y += yGap) {\n for (let x = xStart; x <= plotArea.x + plotArea.width + 0.5; x += xGap) {\n parts.push(\n `<circle cx=\"${r(x)}\" cy=\"${r(y)}\" r=\"1.5\" class=\"xychart-grid\"/>`,\n )\n }\n }\n\n // 2. Bars — always render bar paths inline (before lines for correct z-order)\n // Interactive: also build overlay groups with transparent hit-areas + tooltips (deferred to step 9)\n const barOverlay: string[] = []\n for (const bar of chart.bars) {\n const dataAttrs = ` data-value=\"${bar.value}\"${bar.label ? ` data-label=\"${escapeXml(bar.label)}\"` : ''}`\n const barPath = chart.horizontal\n ? roundedRightBarPath(\n bar.x,\n bar.y,\n bar.width,\n bar.height,\n CHART_FONT.barRadius,\n )\n : roundedTopBarPath(\n bar.x,\n bar.y,\n bar.width,\n bar.height,\n CHART_FONT.barRadius,\n )\n parts.push(\n `<path d=\"${barPath}\" class=\"xychart-bar xychart-color-${bar.colorIndex}\"${dataAttrs}/>`,\n )\n if (interactive) {\n const tipText = formatTipValue(bar.value)\n const tipTitle = bar.label ? `${bar.label}: ${tipText}` : tipText\n const tip = tooltipAbove(bar.x + bar.width / 2, bar.y, tipText)\n barOverlay.push(\n `<g class=\"xychart-bar-group\">` +\n `<rect x=\"${r(bar.x)}\" y=\"${r(bar.y)}\" width=\"${r(bar.width)}\" height=\"${r(bar.height)}\" fill=\"transparent\"/>` +\n `<title>${escapeXml(tipTitle)}</title>` +\n tip +\n `</g>`,\n )\n }\n }\n\n // 3. Lines — shadow first (wider, low opacity), then crisp line on top\n for (const line of chart.lines) {\n if (line.points.length === 0) continue\n const d = smoothCurvePath(line.points)\n parts.push(\n `<path d=\"${d}\" class=\"xychart-line-shadow xychart-color-${line.colorIndex}\" transform=\"translate(0,2)\"/>`,\n )\n parts.push(\n `<path d=\"${d}\" class=\"xychart-line xychart-color-${line.colorIndex}\"/>`,\n )\n }\n\n // 4. Dots — grouped by x-position; interactive groups deferred to overlay\n const dotOverlay: string[] = []\n if (interactive || sparse) {\n // Build legend label lookup: line seriesIndex → \"Line 1\", \"Line 2\", etc.\n const lineLegendLabels = new Map<number, string>()\n for (const item of chart.legend) {\n if (item.type === 'line')\n lineLegendLabels.set(item.seriesIndex, item.label)\n }\n\n type DotEntry = {\n x: number\n y: number\n value: number\n label?: string\n seriesIndex: number\n colorIndex: number\n }\n const columns = new Map<string, DotEntry[]>()\n\n for (const line of chart.lines) {\n for (const p of line.points) {\n const key = r(p.x)\n const column = columns.get(key) ?? []\n columns.set(key, column)\n column.push({\n x: p.x,\n y: p.y,\n value: p.value,\n label: p.label,\n seriesIndex: line.seriesIndex,\n colorIndex: line.colorIndex,\n })\n }\n }\n\n // Every array stored in `columns` was created and immediately pushed to\n // above, so `entries` is never empty here — safe to index entries[0]\n // below without an extra length check.\n for (const entries of columns.values()) {\n const cx = entries[0]!.x\n const label = entries[0]!.label || ''\n\n if (interactive && entries.length > 1) {\n const topY = Math.min(...entries.map((e) => e.y))\n const botY = Math.max(...entries.map((e) => e.y))\n const hitPad = CHART_FONT.dotRadius * 3\n const hitArea = `<rect x=\"${r(cx - hitPad)}\" y=\"${r(topY - hitPad)}\" width=\"${r(hitPad * 2)}\" height=\"${r(botY - topY + hitPad * 2)}\" fill=\"transparent\" class=\"xychart-hit\"/>`\n const tipEntries = entries.map((e) => ({\n text: formatTipValue(e.value),\n legendLabel:\n lineLegendLabels.get(e.seriesIndex) || `Line ${e.seriesIndex + 1}`,\n }))\n const tip = multiTooltipAbove(\n cx,\n topY - CHART_FONT.dotRadius,\n label,\n tipEntries,\n )\n const valStrs = tipEntries.map((e) => e.text)\n const titleText = label\n ? `${label}: ${valStrs.join(' · ')}`\n : valStrs.join(' · ')\n\n let group = `<g class=\"xychart-dot-group\">${hitArea}`\n for (const e of entries) {\n const dataAttrs = ` data-value=\"${e.value}\"${e.label ? ` data-label=\"${escapeXml(e.label)}\"` : ''}`\n group += `<circle cx=\"${r(e.x)}\" cy=\"${r(e.y)}\" r=\"${CHART_FONT.dotRadius}\" class=\"xychart-dot xychart-color-${e.colorIndex}\"${dataAttrs}/>`\n }\n group += `<title>${escapeXml(titleText)}</title>${tip}</g>`\n dotOverlay.push(group)\n } else if (interactive) {\n const e = entries[0]!\n const dataAttrs = ` data-value=\"${e.value}\"${e.label ? ` data-label=\"${escapeXml(e.label)}\"` : ''}`\n const tipText = formatTipValue(e.value)\n const tipTitle = e.label ? `${e.label}: ${tipText}` : tipText\n const tip = tooltipAbove(cx, e.y - CHART_FONT.dotRadius, tipText)\n const hitArea = sparse\n ? `<circle cx=\"${r(cx)}\" cy=\"${r(e.y)}\" r=\"${CHART_FONT.dotRadius * 3}\" fill=\"transparent\" class=\"xychart-hit\"/>`\n : ''\n dotOverlay.push(\n `<g class=\"xychart-dot-group\">${hitArea}` +\n `<circle cx=\"${r(e.x)}\" cy=\"${r(e.y)}\" r=\"${CHART_FONT.dotRadius}\" class=\"xychart-dot xychart-color-${e.colorIndex}\"${dataAttrs}/>` +\n `<title>${escapeXml(tipTitle)}</title>${tip}</g>`,\n )\n } else {\n // Sparse, not interactive: static dots render inline\n for (const e of entries) {\n const dataAttrs = ` data-value=\"${e.value}\"${e.label ? ` data-label=\"${escapeXml(e.label)}\"` : ''}`\n parts.push(\n `<circle cx=\"${r(e.x)}\" cy=\"${r(e.y)}\" r=\"${CHART_FONT.dotRadius}\" class=\"xychart-dot xychart-color-${e.colorIndex}\"${dataAttrs}/>`,\n )\n }\n }\n }\n }\n\n // 5. Axis labels (no axis lines, no tick marks — just floating labels)\n for (const tick of chart.xAxis.ticks) {\n parts.push(\n `<text x=\"${tick.labelX}\" y=\"${tick.labelY}\" text-anchor=\"${tick.textAnchor}\" ` +\n `font-size=\"${CHART_FONT.labelSize}\" font-weight=\"${CHART_FONT.labelWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-label\">${escapeXml(tick.label)}</text>`,\n )\n }\n for (const tick of chart.yAxis.ticks) {\n parts.push(\n `<text x=\"${tick.labelX}\" y=\"${tick.labelY}\" text-anchor=\"${tick.textAnchor}\" ` +\n `font-size=\"${CHART_FONT.labelSize}\" font-weight=\"${CHART_FONT.labelWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-label\">${escapeXml(tick.label)}</text>`,\n )\n }\n\n // 6. Axis titles\n if (chart.xAxis.title) {\n const t = chart.xAxis.title\n const transform = t.rotate\n ? ` transform=\"rotate(${t.rotate},${t.x},${t.y})\"`\n : ''\n parts.push(\n `<text x=\"${t.x}\" y=\"${t.y}\" text-anchor=\"middle\"${transform} ` +\n `font-size=\"${CHART_FONT.axisTitleSize}\" font-weight=\"${CHART_FONT.axisTitleWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-axis-title\">${escapeXml(t.text)}</text>`,\n )\n }\n if (chart.yAxis.title) {\n const t = chart.yAxis.title\n const transform = t.rotate\n ? ` transform=\"rotate(${t.rotate},${t.x},${t.y})\"`\n : ''\n parts.push(\n `<text x=\"${t.x}\" y=\"${t.y}\" text-anchor=\"middle\"${transform} ` +\n `font-size=\"${CHART_FONT.axisTitleSize}\" font-weight=\"${CHART_FONT.axisTitleWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-axis-title\">${escapeXml(t.text)}</text>`,\n )\n }\n\n // 7. Chart title\n if (chart.title) {\n parts.push(\n `<text x=\"${chart.title.x}\" y=\"${chart.title.y}\" text-anchor=\"middle\" ` +\n `font-size=\"${CHART_FONT.titleSize}\" font-weight=\"${CHART_FONT.titleWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-title\">${escapeXml(chart.title.text)}</text>`,\n )\n }\n\n // 8. Legend\n for (const item of chart.legend) {\n const swatchW = 14,\n swatchH = 14\n const gap = 6\n if (item.type === 'bar') {\n parts.push(\n `<rect x=\"${item.x}\" y=\"${item.y - swatchH / 2}\" width=\"${swatchW}\" height=\"${swatchH}\" rx=\"3\" ` +\n `class=\"xychart-bar xychart-color-${item.colorIndex}\"/>`,\n )\n } else {\n const ly = item.y\n parts.push(\n `<line x1=\"${item.x}\" y1=\"${ly}\" x2=\"${item.x + swatchW}\" y2=\"${ly}\" ` +\n `stroke-width=\"${CHART_FONT.lineWidth}\" stroke-linecap=\"round\" class=\"xychart-legend-line xychart-color-${item.colorIndex}\"/>`,\n )\n }\n parts.push(\n `<text x=\"${item.x + swatchW + gap}\" y=\"${item.y}\" text-anchor=\"start\" ` +\n `font-size=\"${CHART_FONT.legendSize}\" font-weight=\"${CHART_FONT.legendWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-label\">${escapeXml(item.label)}</text>`,\n )\n }\n\n // 9. Interactive overlay — rendered last so tooltips are always on top\n for (const g of barOverlay) parts.push(g)\n for (const g of dotOverlay) parts.push(g)\n\n parts.push('</svg>')\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Chart-specific CSS styles\n// ============================================================================\n\nfunction chartStyles(\n chart: PositionedXYChart,\n interactive: boolean,\n sparse: boolean,\n themeAccent?: string,\n bgColor?: string,\n nonce?: string,\n): { style: string; defs: string } {\n const accentHex = themeAccent ?? CHART_ACCENT_FALLBACK\n\n // Collect all unique global color indices from bars + lines\n const colorIndices = new Set<number>()\n for (const b of chart.bars) colorIndices.add(b.colorIndex)\n for (const l of chart.lines) colorIndices.add(l.colorIndex)\n\n // Define --xychart-color-N CSS custom properties (updatable by theme-switching JS)\n // Also define --xychart-bar-fill-N via color-mix() so it stays dynamic on theme change\n const colorVarDefs: string[] = []\n for (const idx of [...colorIndices].sort((a, b) => a - b)) {\n const value =\n idx === 0\n ? `var(--accent, ${CHART_ACCENT_FALLBACK})`\n : getSeriesColor(idx, accentHex, bgColor)\n colorVarDefs.push(` --xychart-color-${idx}: ${value};`)\n colorVarDefs.push(\n ` --xychart-bar-fill-${idx}: color-mix(in srgb, var(--bg) 75%, var(--xychart-color-${idx}) 25%);`,\n )\n }\n\n // Generate unified color rules — one per global index, referencing the CSS vars\n const seriesRules: string[] = []\n for (const idx of [...colorIndices].sort((a, b) => a - b)) {\n const color = `var(--xychart-color-${idx})`\n // Bar-specific: stroke + solid blended fill (no opacity)\n seriesRules.push(\n ` .xychart-bar.xychart-color-${idx} { stroke: ${color}; fill: var(--xychart-bar-fill-${idx}); }`,\n )\n // Line/dot-specific: stroke for paths, fill for circles\n seriesRules.push(\n ` path.xychart-color-${idx}, line.xychart-color-${idx} { stroke: ${color}; }`,\n )\n seriesRules.push(` circle.xychart-color-${idx} { fill: ${color}; }`)\n }\n\n const tipRules = interactive\n ? `\n .xychart-tip { opacity: 0; pointer-events: none; }\n .xychart-tip-bg { fill: var(--_text); filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--fg) 20%, transparent)); }\n .xychart-tip-text { fill: var(--bg); font-size: ${TIP.fontSize}px; font-weight: ${TIP.fontWeight}; }\n .xychart-tip-ptr { fill: var(--_text); }\n .xychart-bar-group:hover .xychart-tip,\n .xychart-dot-group:hover .xychart-tip { opacity: 1; }`\n : ''\n\n const colorVarsBlock =\n colorVarDefs.length > 0 ? `\\n svg {\\n${colorVarDefs.join('\\n')}\\n }` : ''\n\n const style = `${styleOpenTag(nonce)}\n .xychart-grid { fill: var(--_inner-stroke); stroke: none; opacity: 0.65; }\n .xychart-bar { stroke-width: 1.5; }\n .xychart-line { fill: none; stroke-width: ${CHART_FONT.lineWidth}; stroke-linecap: round; stroke-linejoin: round; }\n .xychart-line-shadow { fill: none; stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; opacity: 0.12; }\n .xychart-dot { stroke: var(--bg); stroke-width: 2; }\n .xychart-label { fill: var(--_text-muted); }\n .xychart-axis-title { fill: var(--_text-sec); }\n .xychart-title { fill: var(--_text); }${colorVarsBlock}\n${seriesRules.join('\\n')}${tipRules}\n</style>`\n\n return { style, defs: '' }\n}\n\n// ============================================================================\n// Bar path with all corners rounded\n// ============================================================================\n\nfunction roundedTopBarPath(\n x: number,\n y: number,\n w: number,\n h: number,\n radius: number,\n): string {\n const rr = Math.min(radius, w / 2, h / 2)\n if (rr <= 0) {\n return `M${r(x)},${r(y)} h${r(w)} v${r(h)} h${r(-w)} Z`\n }\n return [\n `M${r(x)},${r(y + rr)}`, // start below top-left\n `Q${r(x)},${r(y)} ${r(x + rr)},${r(y)}`, // top-left\n `L${r(x + w - rr)},${r(y)}`, // top edge\n `Q${r(x + w)},${r(y)} ${r(x + w)},${r(y + rr)}`, // top-right\n `L${r(x + w)},${r(y + h - rr)}`, // right edge\n `Q${r(x + w)},${r(y + h)} ${r(x + w - rr)},${r(y + h)}`, // bottom-right\n `L${r(x + rr)},${r(y + h)}`, // bottom edge\n `Q${r(x)},${r(y + h)} ${r(x)},${r(y + h - rr)}`, // bottom-left\n 'Z',\n ].join(' ')\n}\n\n// ============================================================================\n// Bar path with all corners rounded (for horizontal charts)\n// ============================================================================\n\nfunction roundedRightBarPath(\n x: number,\n y: number,\n w: number,\n h: number,\n radius: number,\n): string {\n const rr = Math.min(radius, w / 2, h / 2)\n if (rr <= 0) {\n return `M${r(x)},${r(y)} h${r(w)} v${r(h)} h${r(-w)} Z`\n }\n return [\n `M${r(x + rr)},${r(y)}`, // start after top-left\n `L${r(x + w - rr)},${r(y)}`, // top edge\n `Q${r(x + w)},${r(y)} ${r(x + w)},${r(y + rr)}`, // top-right\n `L${r(x + w)},${r(y + h - rr)}`, // right edge\n `Q${r(x + w)},${r(y + h)} ${r(x + w - rr)},${r(y + h)}`, // bottom-right\n `L${r(x + rr)},${r(y + h)}`, // bottom edge\n `Q${r(x)},${r(y + h)} ${r(x)},${r(y + h - rr)}`, // bottom-left\n `L${r(x)},${r(y + rr)}`, // left edge\n `Q${r(x)},${r(y)} ${r(x + rr)},${r(y)}`, // top-left\n 'Z',\n ].join(' ')\n}\n\n// ============================================================================\n// Smooth line interpolation — Natural cubic spline\n//\n// Computes the mathematically smoothest curve through all data points by\n// minimizing total curvature (integrated second derivative). Treats y as a\n// function of x, so the curve can never go backwards.\n//\n// Algorithm: tridiagonal system for second derivatives (Thomas algorithm),\n// then convert each cubic segment to SVG cubic Bezier commands.\n// ============================================================================\n\nfunction smoothCurvePath(points: Array<{ x: number; y: number }>): string {\n if (points.length === 0) return ''\n if (points.length === 1) return `M${r(points[0]!.x)},${r(points[0]!.y)}`\n if (points.length === 2) {\n return `M${r(points[0]!.x)},${r(points[0]!.y)} L${r(points[1]!.x)},${r(points[1]!.y)}`\n }\n\n const n = points.length\n\n // From here on, n >= 3 (the n <= 2 cases returned above). Every index used\n // below — including offsets like i-1/i+1 and n-2 — is bound by a `< n - 1`\n // or `< n` loop condition, or is a fixed offset from n itself, into arrays\n // sized exactly `n` or `n - 1`. Non-null assertions on those accesses are\n // the same bounds-checked idiom as loop-index array access elsewhere in\n // this codebase and are left as-is (justified, not asserting past a real\n // gap in type info).\n\n // 1. Interval widths and secant slopes\n const h: number[] = []\n const delta: number[] = []\n for (let i = 0; i < n - 1; i++) {\n h.push(points[i + 1]!.x - points[i]!.x)\n delta.push(h[i]! === 0 ? 0 : (points[i + 1]!.y - points[i]!.y) / h[i]!)\n }\n\n // 2. Solve tridiagonal system for second derivatives c[] (natural boundary: c[0] = c[n-1] = 0)\n const c = new Array<number>(n).fill(0)\n if (n > 2) {\n // Forward elimination\n const cp = new Array<number>(n).fill(0) // modified upper diagonal\n const dp = new Array<number>(n).fill(0) // modified right-hand side\n for (let i = 1; i < n - 1; i++) {\n const diag = 2 * (h[i - 1]! + h[i]!)\n const rhs = 3 * (delta[i]! - delta[i - 1]!)\n if (i === 1) {\n cp[i] = h[i]! / diag\n dp[i] = rhs / diag\n } else {\n const w = diag - h[i - 1]! * cp[i - 1]!\n cp[i] = h[i]! / w\n dp[i] = (rhs - h[i - 1]! * dp[i - 1]!) / w\n }\n }\n // Back substitution\n for (let i = n - 2; i >= 1; i--) {\n c[i] = dp[i]! - cp[i]! * c[i + 1]!\n }\n }\n\n // 3. Compute first derivatives (slopes) at each knot\n const slopes = new Array<number>(n).fill(0)\n for (let i = 0; i < n - 1; i++) {\n slopes[i] = delta[i]! - (h[i]! * (2 * c[i]! + c[i + 1]!)) / 3\n }\n // Slope at last point: derivative of last segment at its end\n slopes[n - 1] = delta[n - 2]! + (h[n - 2]! * c[n - 2]!) / 3\n\n // 4. Convert to cubic Bezier — control points strictly between endpoints in x\n let path = `M${r(points[0]!.x)},${r(points[0]!.y)}`\n for (let i = 0; i < n - 1; i++) {\n const seg = h[i]! / 3\n const cp1x = points[i]!.x + seg\n const cp1y = points[i]!.y + slopes[i]! * seg\n const cp2x = points[i + 1]!.x - seg\n const cp2y = points[i + 1]!.y - slopes[i + 1]! * seg\n path += ` C${r(cp1x)},${r(cp1y)} ${r(cp2x)},${r(cp2y)} ${r(points[i + 1]!.x)},${r(points[i + 1]!.y)}`\n }\n\n return path\n}\n\n// ============================================================================\n// Tooltip rendering\n// ============================================================================\n\n/**\n * Multi-value tooltip: category label on top, each series value below with legend text label.\n */\nfunction multiTooltipAbove(\n cx: number,\n topY: number,\n label: string,\n entries: Array<{ text: string; legendLabel: string }>,\n): string {\n const lineH = 20\n const padY = 6\n const labelGap = 10\n const headingW = estimateTextWidth(label, TIP.fontSize, 600)\n const maxRowW = Math.max(\n ...entries.map((e) => {\n const legendW = estimateTextWidth(\n e.legendLabel,\n TIP.fontSize,\n TIP.fontWeight,\n )\n const valW = estimateTextWidth(e.text, TIP.fontSize, TIP.fontWeight)\n return legendW + labelGap + valW\n }),\n )\n const bgW = Math.max(headingW, maxRowW) + TIP.padX * 2\n const bgH = padY + lineH + entries.length * lineH + padY\n\n const tipY = Math.max(TIP.minY, topY - TIP.offsetY - bgH - TIP.pointerSize)\n const bgX = cx - bgW / 2\n\n const ptrX = cx\n const ptrY = tipY + bgH\n const ps = TIP.pointerSize\n const pointer = `<polygon points=\"${r(ptrX - ps)},${r(ptrY)} ${r(ptrX + ps)},${r(ptrY)} ${r(ptrX)},${r(ptrY + ps)}\" class=\"xychart-tip xychart-tip-ptr\"/>`\n\n let svg = `<rect x=\"${r(bgX)}\" y=\"${r(tipY)}\" width=\"${r(bgW)}\" height=\"${bgH}\" rx=\"${TIP.rx}\" class=\"xychart-tip xychart-tip-bg\"/>`\n svg += pointer\n\n // Category label (bold, centered)\n let textY = tipY + padY + lineH / 2\n svg += `<text x=\"${r(cx)}\" y=\"${r(textY)}\" text-anchor=\"middle\" font-weight=\"600\" font-size=\"${TIP.fontSize}\" dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-tip xychart-tip-text\">${escapeXml(label)}</text>`\n\n // Value lines: legend label left-aligned, value right-aligned\n const rowLeft = bgX + TIP.padX\n const rowRight = bgX + bgW - TIP.padX\n for (const entry of entries) {\n textY += lineH\n svg += `<text x=\"${r(rowLeft)}\" y=\"${r(textY)}\" text-anchor=\"start\" font-size=\"${TIP.fontSize}\" font-weight=\"${TIP.fontWeight}\" dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-tip xychart-tip-text\">${escapeXml(entry.legendLabel)}</text>`\n svg += `<text x=\"${r(rowRight)}\" y=\"${r(textY)}\" text-anchor=\"end\" font-size=\"${TIP.fontSize}\" font-weight=\"${TIP.fontWeight}\" dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-tip xychart-tip-text\">${escapeXml(entry.text)}</text>`\n }\n\n return svg\n}\n\nfunction tooltipAbove(cx: number, topY: number, text: string): string {\n const textW = estimateTextWidth(text, TIP.fontSize, TIP.fontWeight)\n const bgW = textW + TIP.padX * 2\n const bgH = TIP.height\n const tipY = Math.max(TIP.minY, topY - TIP.offsetY - bgH - TIP.pointerSize)\n const bgX = cx - bgW / 2\n const textX = cx\n const textY = tipY + bgH / 2\n\n const ptrX = cx\n const ptrY = tipY + bgH\n const ps = TIP.pointerSize\n const pointer = `<polygon points=\"${r(ptrX - ps)},${r(ptrY)} ${r(ptrX + ps)},${r(ptrY)} ${r(ptrX)},${r(ptrY + ps)}\" class=\"xychart-tip xychart-tip-ptr\"/>`\n\n return (\n `<rect x=\"${r(bgX)}\" y=\"${r(tipY)}\" width=\"${r(bgW)}\" height=\"${bgH}\" rx=\"${TIP.rx}\" class=\"xychart-tip xychart-tip-bg\"/>` +\n pointer +\n `<text x=\"${r(textX)}\" y=\"${r(textY)}\" text-anchor=\"middle\" dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-tip xychart-tip-text\">${escapeXml(text)}</text>`\n )\n}\n\nfunction formatTipValue(v: number): string {\n if (Number.isInteger(v)) return v.toLocaleString('en-US')\n return v.toFixed(Math.abs(v) < 10 ? 1 : 0)\n}\n\nfunction r(n: number): string {\n return String(Math.round(n * 10) / 10)\n}\n\nfunction escapeXml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n","import type {\n MermaidGraph,\n MermaidNode,\n MermaidSubgraph,\n NodeShape,\n EdgeStyle,\n Statement,\n} from '@zombie-mermaid/core'\nimport {\n toDirection,\n normalizeBrTags,\n splitStatementsByLine,\n extractInitConfig,\n applyClickStatement as applyClickStatementShared,\n parseStyleProps,\n tryApplyClassDef,\n tryApplyClassAssignment,\n tryApplyStyleStatement,\n} from '@zombie-mermaid/core'\n\nimport {\n matchExpandedBlock,\n parseExpandedMeta,\n resolveShapeName,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { ExpandedNodeMeta } from '@zombie-mermaid/mermaid-parser'\n/** Remove a single layer of matching wrapping quotes (`\"…\"` or `'…'`). */\nfunction stripWrappingQuotes(s: string): string {\n const t = s.trim()\n if (\n t.length >= 2 &&\n ((t[0] === '\"' && t[t.length - 1] === '\"') ||\n (t[0] === \"'\" && t[t.length - 1] === \"'\"))\n ) {\n return t.slice(1, -1)\n }\n return t\n}\n\n// ============================================================================\n// Mermaid parser — flowcharts and state diagrams\n//\n// Supports:\n// Flowcharts: graph TD / flowchart LR\n// State diagrams: stateDiagram-v2\n//\n// Line-by-line regex approach — the grammar is regular enough\n// that we don't need a grammar generator or full parser combinator.\n// ============================================================================\n\n// `toDirection` used to be defined here, with this file as its only caller.\n// #624 gave it a second caller — `packages/mermaid-parser/src/er/parser.ts`,\n// which cannot import this umbrella file without creating a cycle\n// (mermaid-parser -> umbrella -> mermaid-parser) — so it moved to\n// `packages/core/src/direction.ts` alongside `isDirection`, for the same\n// reason that one moved under #625. Re-exported here so existing importers\n// of `toDirection` from `./parser.ts` (this file's own tests included) keep\n// working unchanged.\nexport { toDirection } from '@zombie-mermaid/core'\n\n/**\n * All diagram-type headers this library recognizes, for the \"supported\n * headers\" list in the invalid-header error below. Kept in one place so a\n * newly-added diagram type doesn't get forgotten in the error message the\n * way sequence/class/ER/xychart were before issue #541.\n */\nconst SUPPORTED_HEADERS =\n '\"graph <dir>\"/\"flowchart <dir>\" (dir: TD, TB, LR, BT, RL), \"stateDiagram-v2\", \"sequenceDiagram\", \"classDiagram\", \"erDiagram\", \"xychart-beta\"'\n\n/**\n * Best-guess canonical header for a header line that looks like an attempt\n * at one of the *other* diagram types this library supports, but didn't\n * match `detectDiagramType`'s exact-match regex (a typo, extra trailing\n * text, wrong case pattern, etc.) and so fell through to this flowchart\n * parser as the default. Returns undefined when `header` doesn't look like\n * any of those — in which case the generic \"supported headers\" list in the\n * thrown error is the best we can do.\n *\n * This only recognizes the four *other* multi-word headers\n * (sequenceDiagram/classDiagram/erDiagram/xychart-beta/stateDiagram-v2);\n * \"graph\"/\"flowchart\" typos are handled by the direction-specific message\n * below instead, since those already matched the diagram-type keyword and\n * just have a bad or missing direction token.\n */\nfunction suggestedHeaderFor(header: string): string | undefined {\n const lower = header.trim().toLowerCase()\n if (/^sequence/.test(lower)) return 'sequenceDiagram'\n if (/^class/.test(lower)) return 'classDiagram'\n if (/^er/.test(lower)) return 'erDiagram'\n if (/^xychart/.test(lower)) return 'xychart-beta'\n if (/^state/.test(lower)) return 'stateDiagram-v2'\n return undefined\n}\n\n/**\n * Matches the start of a statement that continues the *previous* one's edge\n * chain rather than beginning a new one: Mermaid lets a vertex-chain\n * statement break across lines, with the link operator leading the next\n * line — e.g.\n *\n * start([Start])\n * ==> green([Change some code])\n * ==> finish([Finish])\n *\n * is one chained statement (`start ==> green ==> finish`), identical to\n * writing it on one line. `splitStatementsByLine` only knows about newlines\n * and `;` as separators, so without this it hands `parseFlowchart` three\n * unrelated-looking statements — the continuation lines start with a bare\n * arrow and no node group, so `parseEdgeLine` can't find a source node and\n * drops them entirely (see issue mermaid-js/mermaid#6049's repro, reported\n * against this parser).\n *\n * Covers every arrow opener `parseEdgeLine` itself recognizes (ARROW_REGEX /\n * TEXT_ARROW_REGEX below): a solid/thick run (`--`, `===`), a dotted run\n * (`-.`, `-.-`), a `~~~` run, each optionally preceded by a `<` marker and/or\n * an edge id (`e1@-->`).\n *\n * Deliberately excludes the `o`/`x` start markers ARROW_REGEX also accepts\n * (`o--o`, `x--x`): unlike `<`, both are also valid bare node ids, so a\n * first-body-line statement like `x-->Y` would otherwise be misread as an\n * `x`-marked continuation of the header and merged into it, corrupting the\n * header line (`flowchart TD x-->Y`) instead of parsing `x` as its own node.\n * A genuine marked-start continuation line is rare enough that losing it is\n * the safer tradeoff.\n */\nconst CONTINUATION_START_REGEX = /^(?:[\\w-]+@)?<?(?:-{2,}|={2,}|-\\.+-?|~{3,})/\n\n/**\n * Rejoin a continuation statement onto the one it continues.\n *\n * `splitStatementsByLine` groups statements by their originating physical\n * line, which is what makes this safe: only the *first* statement in a\n * group is eligible to merge backward into the previous group's last\n * statement (a genuine multi-line continuation, see\n * `CONTINUATION_START_REGEX`). A later statement in the same group got\n * there via an explicit `;` on that line (`A --> B; --> C`) — that boundary\n * must stay a boundary, or `--> C` would wrongly become part of the chain\n * instead of the source-less fragment it actually is.\n */\nfunction mergeContinuationLines(groups: Statement[][]): Statement[] {\n const merged: Statement[] = []\n for (const group of groups) {\n group.forEach((statement, index) => {\n if (\n index === 0 &&\n merged.length > 0 &&\n CONTINUATION_START_REGEX.test(statement.text)\n ) {\n // Keep the *first* line's number: a merged statement is reported at\n // the line its logical statement began, not the continuation line.\n const prev = merged[merged.length - 1]!\n merged[merged.length - 1] = {\n text: `${prev.text} ${statement.text}`,\n line: prev.line,\n }\n } else {\n merged.push(statement)\n }\n })\n }\n return merged\n}\n\n/**\n * Parse Mermaid text into a logical graph structure.\n * Auto-detects diagram type (flowchart or state diagram).\n * Throws on invalid/unsupported input.\n */\nexport function parseMermaid(text: string): MermaidGraph {\n /*\n * Init directives must be read from the raw lines: `%%{init: ...}%%` begins\n * with `%%`, so splitStatementsByLine — which treats `%%` as a comment and\n * truncates the line there — would otherwise discard it along with real\n * comments before it could be seen.\n */\n const initConfig = extractInitConfig(text.split('\\n').map((l) => l.trim()))\n\n const lines = mergeContinuationLines(splitStatementsByLine(text))\n\n if (lines.length === 0) {\n throw new Error('Empty mermaid diagram')\n }\n\n // Detect diagram type from header\n const headerStmt = lines[0]!\n\n // State diagram: \"stateDiagram-v2\" or \"stateDiagram\"\n const graph = /^stateDiagram(-v2)?\\s*$/i.test(headerStmt.text)\n ? parseStateDiagram(lines)\n : parseFlowchart(lines)\n\n graph.initConfig = initConfig\n return graph\n}\n\n// ============================================================================\n// Flowchart parser\n// ============================================================================\n\nfunction parseFlowchart(lines: Statement[]): MermaidGraph {\n // parseFlowchart is only ever invoked by parseMermaid, which has already\n // verified `lines` is non-empty — but that invariant isn't visible to the\n // type checker across the function boundary, so validate it here instead\n // of asserting past it.\n const headerStmt = lines[0]\n if (headerStmt === undefined) {\n /* v8 ignore next */\n throw new Error('parseFlowchart called with no lines')\n }\n const header = headerStmt.text\n\n const headerMatch = header.match(\n /^(?:graph|flowchart)\\s+(TD|TB|LR|BT|RL)\\s*$/i,\n )\n if (!headerMatch) {\n // A header that starts with \"graph\"/\"flowchart\" but has a bad or\n // missing direction gets a targeted message about the direction token\n // specifically — it already picked the right diagram type.\n const partialFlowchartMatch = header.match(\n /^(?:graph|flowchart)\\b\\s*(.*)$/i,\n )\n if (partialFlowchartMatch) {\n const rest = partialFlowchartMatch[1]!.trim()\n throw new Error(\n rest.length > 0\n ? `Line ${headerStmt.line}: Invalid direction \"${rest}\" in header \"${header}\". Expected one of: TD, TB, LR, BT, RL.`\n : `Line ${headerStmt.line}: Missing direction in header \"${header}\". Expected e.g. \"graph TD\" or \"flowchart LR\" — one of: TD, TB, LR, BT, RL.`,\n )\n }\n\n // Otherwise this header didn't match any recognized diagram-type\n // keyword at all (typo, extra text, or genuinely unsupported syntax) —\n // point at the closest known diagram type when the text resembles one.\n const suggestion = suggestedHeaderFor(header)\n const hint =\n suggestion && suggestion.toLowerCase() !== header.trim().toLowerCase()\n ? ` Did you mean \"${suggestion}\"?`\n : ''\n throw new Error(\n `Line ${headerStmt.line}: Invalid mermaid header: \"${header}\".${hint} Supported headers: ${SUPPORTED_HEADERS}.`,\n )\n }\n\n const direction = toDirection(headerMatch[1])\n\n const graph: MermaidGraph = {\n direction,\n nodes: new Map(),\n edges: [],\n subgraphs: [],\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n\n // Subgraph stack for nested subgraphs.\n const subgraphStack: MermaidSubgraph[] = []\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- classDef / class assignment / style — shared with the class-diagram\n // parser, see packages/core/src/style-directives.ts ---\n if (tryApplyClassDef(line, graph)) continue\n if (tryApplyClassAssignment(line, graph)) continue\n if (tryApplyStyleStatement(line, graph)) continue\n\n // --- click interaction: `click A \"url\" \"tooltip\" _blank` / `click A call fn()` ---\n if (/^click\\s+/i.test(line)) {\n applyClickStatement(line, graph)\n continue\n }\n\n // --- edge metadata: `e1@{ animate: true }` — shared with parseStateDiagram ---\n if (tryApplyEdgeMetaLine(line, graph)) {\n continue\n }\n\n // --- linkStyle: `linkStyle 0 stroke:#f00` or `linkStyle default stroke:#f00` ---\n const linkStyleMatch = line.match(/^linkStyle\\s+(default|[\\d,\\s]+)\\s+(.+)$/)\n if (linkStyleMatch) {\n const target = linkStyleMatch[1]!.trim()\n const props = parseStyleProps(linkStyleMatch[2]!)\n if (target === 'default') {\n graph.linkStyles.set('default', {\n ...graph.linkStyles.get('default'),\n ...props,\n })\n } else {\n const indices = target.split(',').map((s) => parseInt(s.trim(), 10))\n for (const idx of indices) {\n if (!isNaN(idx)) {\n graph.linkStyles.set(idx, {\n ...graph.linkStyles.get(idx),\n ...props,\n })\n }\n }\n }\n continue\n }\n\n // --- direction override inside subgraph: `direction LR` ---\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch && subgraphStack.length > 0) {\n subgraphStack[subgraphStack.length - 1]!.direction = toDirection(\n dirMatch[1],\n )\n continue\n }\n\n // --- subgraph start: `subgraph Label` or `subgraph id [Label]` ---\n const subgraphMatch = line.match(/^subgraph\\s+(.+)$/)\n if (subgraphMatch) {\n const rest = subgraphMatch[1]!.trim()\n // Check for `subgraph id [Label]` / `subgraph id [\"Label\"]` form. The id\n // may contain non-ASCII chars (e.g. CJK), so match any run of non-space,\n // non-`[` chars rather than ASCII [\\w-]; strip optional quotes wrapping\n // the title.\n const bracketMatch = rest.match(/^([^\\s[]+)\\s*\\[(.+)\\]$/)\n let id: string\n let label: string\n if (bracketMatch) {\n id = bracketMatch[1]!\n label = normalizeBrTags(stripWrappingQuotes(bracketMatch[2]!))\n } else {\n // `subgraph Label` (optionally quoted): the label doubles as the id,\n // slugified — but preserve unicode letters/numbers so a CJK-only title\n // doesn't collapse to an empty id (which broke nested layout).\n label = normalizeBrTags(stripWrappingQuotes(rest))\n id = label.replace(/\\s+/g, '_').replace(/[^\\p{L}\\p{N}_-]/gu, '')\n }\n const sg: MermaidSubgraph = { id, label, nodeIds: [], children: [] }\n subgraphStack.push(sg)\n continue\n }\n\n // --- subgraph end ---\n if (line === 'end') {\n const completed = subgraphStack.pop()\n if (completed) {\n if (subgraphStack.length > 0) {\n subgraphStack[subgraphStack.length - 1]!.children.push(completed)\n } else {\n graph.subgraphs.push(completed)\n }\n }\n continue\n }\n\n // --- Edge/node definitions ---\n parseEdgeLine(line, graph, subgraphStack)\n }\n\n return graph\n}\n\n// ============================================================================\n// State diagram parser\n//\n// Supported syntax:\n// stateDiagram-v2\n// s1 : Description\n// state \"Description\" as s1\n// s1 --> s2 : label\n// [*] --> s1 (start pseudostate)\n// s1 --> [*] (end pseudostate)\n// state CompositeState {\n// inner1 --> inner2\n// }\n// ============================================================================\n\nfunction parseStateDiagram(lines: Statement[]): MermaidGraph {\n const graph: MermaidGraph = {\n direction: 'TD',\n nodes: new Map(),\n edges: [],\n subgraphs: [],\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n\n // Track composite state nesting (like subgraphs)\n const compositeStack: MermaidSubgraph[] = []\n // Track all composite state IDs to avoid creating duplicate nodes\n const compositeStateIds = new Set<string>()\n // Counter for unique [*] pseudostate IDs\n let startCount = 0\n let endCount = 0\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- direction override ---\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch) {\n if (compositeStack.length > 0) {\n compositeStack[compositeStack.length - 1]!.direction = toDirection(\n dirMatch[1],\n )\n } else {\n graph.direction = toDirection(dirMatch[1])\n }\n continue\n }\n\n // --- linkStyle: `linkStyle 0 stroke:#f00` or `linkStyle default stroke:#f00` ---\n const linkStyleMatch = line.match(/^linkStyle\\s+(default|[\\d,\\s]+)\\s+(.+)$/)\n if (linkStyleMatch) {\n const target = linkStyleMatch[1]!.trim()\n const props = parseStyleProps(linkStyleMatch[2]!)\n if (target === 'default') {\n graph.linkStyles.set('default', {\n ...graph.linkStyles.get('default'),\n ...props,\n })\n } else {\n const indices = target.split(',').map((s) => parseInt(s.trim(), 10))\n for (const idx of indices) {\n if (!isNaN(idx)) {\n graph.linkStyles.set(idx, {\n ...graph.linkStyles.get(idx),\n ...props,\n })\n }\n }\n }\n continue\n }\n\n // --- edge metadata: `e1@{ animate: true }` — shared with parseFlowchart ---\n if (tryApplyEdgeMetaLine(line, graph)) {\n continue\n }\n\n // --- composite state start: `state CompositeState {` ---\n const compositeMatch = line.match(\n /^state\\s+(?:\"([^\"]+)\"\\s+as\\s+)?([\\w\\p{L}]+)\\s*\\{$/u,\n )\n if (compositeMatch) {\n const label = compositeMatch[1] ?? compositeMatch[2]!\n const id = compositeMatch[2]!\n const sg: MermaidSubgraph = { id, label, nodeIds: [], children: [] }\n compositeStack.push(sg)\n // Track this ID to avoid creating a duplicate node for the composite state\n compositeStateIds.add(id)\n // Remove any existing node that was created when parsing transitions before\n // this composite state definition (e.g., \"A --> Processing\" before \"state Processing {\")\n graph.nodes.delete(id)\n continue\n }\n\n // --- composite state end ---\n if (line === '}') {\n const completed = compositeStack.pop()\n if (completed) {\n if (compositeStack.length > 0) {\n compositeStack[compositeStack.length - 1]!.children.push(completed)\n } else {\n graph.subgraphs.push(completed)\n }\n }\n continue\n }\n\n // --- state alias: `state \"Description\" as s1` (without brace) ---\n const stateAliasMatch = line.match(\n /^state\\s+\"([^\"]+)\"\\s+as\\s+([\\w\\p{L}]+)\\s*$/u,\n )\n if (stateAliasMatch) {\n const label = normalizeBrTags(stateAliasMatch[1]!)\n const id = stateAliasMatch[2]!\n registerStateNode(graph, compositeStack, { id, label, shape: 'rounded' })\n continue\n }\n\n /*\n * --- transition: `s1 --> s2`, `s1 --> s2 : label`, `[*] --> s1`, or\n * with an edge id (Mermaid v11.10.0+): `s1 e1@--> s2` ---\n *\n * State-diagram transitions only ever use `-->` (unlike flowchart's\n * many arrow variants), so the edge id — same `id@` prefix syntax as\n * flowchart's `A e1@--> B` — is captured inline in this one regex\n * rather than needing flowchart's separate pre-arrow scan.\n */\n const transitionMatch = line.match(\n /^(\\[\\*\\]|[\\w\\p{L}-]+)\\s*(?:([\\w-]+)@)?-->\\s*(\\[\\*\\]|[\\w\\p{L}-]+)(?:\\s*:\\s*(.+))?$/u,\n )\n if (transitionMatch) {\n let sourceId = transitionMatch[1]!\n const edgeId = transitionMatch[2]\n let targetId = transitionMatch[3]!\n const rawTransitionLabel = transitionMatch[4]?.trim()\n const edgeLabel = rawTransitionLabel\n ? normalizeBrTags(rawTransitionLabel)\n : undefined\n\n // Handle [*] pseudostates — each occurrence gets a unique ID\n if (sourceId === '[*]') {\n startCount++\n sourceId = `_start${startCount > 1 ? startCount : ''}`\n registerStateNode(graph, compositeStack, {\n id: sourceId,\n label: '',\n shape: 'state-start',\n })\n } else if (!compositeStateIds.has(sourceId)) {\n // Only create a node if this isn't a composite state\n ensureStateNode(graph, compositeStack, sourceId)\n }\n\n if (targetId === '[*]') {\n endCount++\n targetId = `_end${endCount > 1 ? endCount : ''}`\n registerStateNode(graph, compositeStack, {\n id: targetId,\n label: '',\n shape: 'state-end',\n })\n } else if (!compositeStateIds.has(targetId)) {\n // Only create a node if this isn't a composite state\n ensureStateNode(graph, compositeStack, targetId)\n }\n\n graph.edges.push({\n source: sourceId,\n target: targetId,\n label: edgeLabel,\n style: 'solid',\n hasArrowStart: false,\n hasArrowEnd: true,\n ...(edgeId !== undefined ? { id: edgeId } : {}),\n })\n continue\n }\n\n // --- state description: `s1 : Description` ---\n const stateDescMatch = line.match(/^([\\w\\p{L}-]+)\\s*:\\s*(.+)$/u)\n if (stateDescMatch) {\n const id = stateDescMatch[1]!\n const label = normalizeBrTags(stateDescMatch[2]!.trim())\n registerStateNode(graph, compositeStack, { id, label, shape: 'rounded' })\n continue\n }\n }\n\n return graph\n}\n\n/** Register a state node and track in composite state if applicable */\nfunction registerStateNode(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n node: MermaidNode,\n): void {\n const isNew = !graph.nodes.has(node.id)\n if (isNew) {\n graph.nodes.set(node.id, node)\n }\n if (compositeStack.length > 0) {\n const current = compositeStack[compositeStack.length - 1]!\n if (!current.nodeIds.includes(node.id)) {\n current.nodeIds.push(node.id)\n }\n }\n}\n\n/** Ensure a state node exists with default rounded shape */\nfunction ensureStateNode(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n id: string,\n): void {\n if (!graph.nodes.has(id)) {\n registerStateNode(graph, compositeStack, {\n id,\n label: id,\n shape: 'rounded',\n })\n } else {\n // Track in composite if applicable\n if (compositeStack.length > 0) {\n const current = compositeStack[compositeStack.length - 1]!\n if (!current.nodeIds.includes(id)) {\n current.nodeIds.push(id)\n }\n }\n }\n}\n\n// ============================================================================\n// Flowchart edge line parser\n//\n// Handles chained edges like: A[Label] --> B(Label) -.-> C{Label}\n// Also handles & parallel links: A & B --> C & D\n// ============================================================================\n\n/**\n * Arrow regex — matches all arrow operators with optional labels.\n *\n * Supported operators:\n * --> --- solid arrow / solid line\n * -.-> -.- dotted arrow / dotted line\n * ==> === thick arrow / thick line\n * <--> <-.-> <==> bidirectional variants\n * --o --x circle-end / cross-end arrow (see issue #65)\n * o-- x-- circle-start / cross-start arrow\n * o--o x--x circle/cross at both ends\n *\n * The doubled `o--o`/`x--x` forms must be tried before the single-sided\n * `o--`/`x--` forms — regex alternation picks the first alternative that\n * matches, not the longest, so `o--` listed first would consume just the\n * start marker and strand the trailing `o` as a bogus token. Capturing the\n * markers as their own groups (rather than baking them into an alternation\n * of whole tokens) sidesteps that ordering problem entirely.\n *\n * The body is a variable-length run: Mermaid uses extra characters as a\n * layout-rank hint (`A ---- B` pushes B one rank further than `A --> B`).\n * A fixed alternation of `-->|---|==>` mis-tokenized anything longer,\n * stranding the surplus characters and corrupting the following token.\n * The run length is parsed so the edge survives; the rank hint it encodes\n * is not yet modelled by the layout engine.\n *\n * `~~~` is Mermaid's invisible link: it participates in layout but draws\n * nothing.\n *\n * Optional label: -->|label text|\n */\nconst ARROW_REGEX = /^(<|o|x)?(-{2,}|={2,}|-\\.+-|~{3,})(>|o|x)?(?:\\|([^|]*)\\|)?/\n\n/**\n * Link bodies that are only a link when a start or end marker accompanies\n * them.\n *\n * A bare `--` (or `==`) opens Mermaid's text-embedded label syntax —\n * `A -- Yes --> B` — which TEXT_ARROW_REGEX handles. Treating it as an\n * unmarked open link here would consume the opener and strand the label.\n * Mermaid itself requires three characters for an unmarked open link\n * (`A --- B`), so this only rejects what Mermaid also rejects.\n */\nconst AMBIGUOUS_UNMARKED_BODIES = new Set(['--', '=='])\n\n/**\n * Map a raw start/end marker character (`<`, `>`, `o`, `x`, or undefined) to\n * the distinct terminator shape it draws, if any.\n *\n * `<`/`>` are plain arrowheads — already handled by `hasArrowStart`/\n * `hasArrowEnd` — so they map to `undefined` here, same as no marker at all.\n * Only `o`/`x` need their own shape (circle/cross) recorded, so the ASCII\n * renderer can draw something other than the default arrowhead glyph — see\n * issue #330.\n */\nfunction markerKind(\n marker: string | undefined,\n): 'circle' | 'cross' | undefined {\n if (marker === 'o') return 'circle'\n if (marker === 'x') return 'cross'\n return undefined\n}\n\n/**\n * Text-embedded label regex — matches \"-- label -->\", \"-. label .->\", \"== label ==>\" syntax.\n * Tried as fallback when ARROW_REGEX doesn't match.\n *\n * The closing operator is a variable-length run, matching ARROW_REGEX. While\n * it was a fixed alternation, `A -- label ----> B` consumed only `---` from\n * `---->`, leaving `-> B`, which forms no node group — so the edge and its\n * target node were both dropped silently. Exactly the failure mode the\n * variable-length work exists to remove.\n *\n * Based on PR #36 by @liuxiaopai-ai (https://github.com/lukilabs/beautiful-mermaid/pull/36)\n */\nconst TEXT_ARROW_REGEX =\n /^(<|o|x)?(--|-\\.|==)\\s+(.+?)\\s+(-{2,}[>ox]|={2,}[>ox]|\\.+-[>ox]|-{3,}|={3,}|-\\.+-)/\n\n/**\n * Node shape patterns — ordered from most specific delimiters to least.\n * Multi-char delimiters must be tried before single-char to avoid false matches.\n *\n * The label-content group for each shape is quote-aware: it matches either a\n * complete `\"...\"` quoted span (any character, including this shape's own\n * closing delimiter chars, is fine inside quotes) or a single character that\n * isn't the start of the closing delimiter. This stops the scanner from\n * treating a `]`/`)`/`}` etc. *inside* a quoted label as the node's real\n * closing bracket (see issue #61) — without this, `A[\"test [] brackets\"]`\n * would stop at the first `]`, which lives inside the quoted string.\n */\n/**\n * A node shape pattern.\n *\n * `shape` is usually a fixed `NodeShape`. For the slash-bracket family it is\n * a function instead, because those four shapes are distinguished by which\n * delimiter *closes* them — which the regex has to capture rather than\n * hard-code. See SLASH_BRACKET note below.\n */\ninterface NodePattern {\n regex: RegExp\n shape: NodeShape | ((closingDelimiter: string) => NodeShape)\n}\n\nconst NODE_PATTERNS: NodePattern[] = [\n // Triple delimiters (must be first)\n {\n regex: /^([\\w\\p{L}-]+)\\(\\(\\(((?:\"[^\"]*\"|(?!\\)\\)\\)).)+)\\)\\)\\)/u,\n shape: 'doublecircle',\n }, // A(((text)))\n\n // Double delimiters with mixed brackets\n {\n regex: /^([\\w\\p{L}-]+)\\(\\[((?:\"[^\"]*\"|(?!\\]\\)).)+)\\]\\)/u,\n shape: 'stadium',\n }, // A([text])\n { regex: /^([\\w\\p{L}-]+)\\(\\(((?:\"[^\"]*\"|(?!\\)\\)).)+)\\)\\)/u, shape: 'circle' }, // A((text))\n {\n regex: /^([\\w\\p{L}-]+)\\[\\[((?:\"[^\"]*\"|(?!\\]\\]).)+)\\]\\]/u,\n shape: 'subroutine',\n }, // A[[text]]\n {\n regex: /^([\\w\\p{L}-]+)\\[\\(((?:\"[^\"]*\"|(?!\\)\\]).)+)\\)\\]/u,\n shape: 'cylinder',\n }, // A[(text)]\n\n /*\n * SLASH_BRACKET family — must come before plain [text].\n *\n * Four shapes share a `[` + slash opener and differ only in which\n * delimiter closes them:\n *\n * A[/text\\] trapezoid A[/text/] parallelogram\n * A[\\text/] trapezoid-alt A[\\text\\] parallelogram-alt\n *\n * They CANNOT be four separate patterns each negated against its own\n * closing pair. A pattern for `[/…\\]` whose content merely excludes `\\]`\n * will happily run past a `/]` to reach a `\\]` later on the same line, so\n *\n * A[/parallelogram/] --> B[\\alt\\]\n *\n * matched as a single trapezoid whose label was the entire statement —\n * silently swallowing the edge and the second node. Ordering the patterns\n * differently only moves which input breaks.\n *\n * Instead, one pattern per opener stops at whichever slash-close comes\n * first and captures it, and the captured delimiter selects the shape.\n */\n {\n regex: /^([\\w\\p{L}-]+)\\[\\/((?:\"[^\"]*\"|(?![\\\\/]\\]).)+)([\\\\/])\\]/u,\n shape: (close) => (close === '\\\\' ? 'trapezoid' : 'parallelogram'),\n }, // A[/text\\] or A[/text/]\n {\n regex: /^([\\w\\p{L}-]+)\\[\\\\((?:\"[^\"]*\"|(?![\\\\/]\\]).)+)([\\\\/])\\]/u,\n shape: (close) => (close === '/' ? 'trapezoid-alt' : 'parallelogram-alt'),\n }, // A[\\text/] or A[\\text\\]\n\n // Asymmetric flag shape\n { regex: /^([\\w\\p{L}-]+)>((?:\"[^\"]*\"|(?!\\]).)+)\\]/u, shape: 'asymmetric' }, // A>text]\n\n // Double curly braces (hexagon) — must come before single {text}\n {\n regex: /^([\\w\\p{L}-]+)\\{\\{((?:\"[^\"]*\"|(?!\\}\\}).)+)\\}\\}/u,\n shape: 'hexagon',\n }, // A{{text}}\n\n // Single-char delimiters (last — most common, least specific)\n { regex: /^([\\w\\p{L}-]+)\\[((?:\"[^\"]*\"|(?!\\]).)+)\\]/u, shape: 'rectangle' }, // A[text]\n { regex: /^([\\w\\p{L}-]+)\\(((?:\"[^\"]*\"|(?!\\)).)+)\\)/u, shape: 'rounded' }, // A(text)\n { regex: /^([\\w\\p{L}-]+)\\{((?:\"[^\"]*\"|(?!\\}).)+)\\}/u, shape: 'diamond' }, // A{text}\n]\n\n/**\n * Regex for a bare node reference (just an ID, no shape brackets).\n *\n * Only allows a hyphen when it's sandwiched between word characters\n * (`foo-bar`, `step-1-b`), never a bare/trailing/doubled hyphen. This keeps\n * legitimately hyphenated ids intact while stopping the id from swallowing\n * the leading dashes of an immediately-following arrow when there's no\n * whitespace before it — e.g. `A-->B` (see issue #61): the naive `[\\w-]+`\n * greedily consumed `A--`, leaving a bogus node and no edge. Arrow tokens\n * always start with `-`/`=`/`<` followed by another non-word character\n * (`-`, `.`, `=`, `>`), which this pattern never matches into, so it now\n * stops cleanly at `A` and lets the arrow regex take over.\n *\n * `\\p{L}` (any Unicode letter, alongside plain `\\w`) is included so a bare\n * non-ASCII name (`Lasaña`, `日本`) matches in full instead of truncating at\n * the first non-ASCII character and silently stranding the rest of the line\n * as unparsed text — see issue #328. This mirrors the state-diagram\n * transition regex below, which already allows `\\p{L}` for the same reason.\n */\nconst BARE_NODE_REGEX = /^([\\w\\p{L}]+(?:-[\\w\\p{L}]+)*)/u\n\n/**\n * Node id immediately followed by the expanded-syntax opener: `A@{`.\n *\n * Only the id is captured — the block itself needs depth- and quote-aware\n * scanning (a label may contain `}`), which a regex would do badly, so\n * `matchExpandedBlock` takes over from here.\n */\nconst EXPANDED_NODE_ID_REGEX = /^([\\w\\p{L}-]+)(?=@\\{)/u\n\n/**\n * Resolve the geometry for an `A@{ ... }` node.\n *\n * An `icon:` or `img:` node has no `shape:` of its own — its outline comes\n * from `form:` instead (Mermaid defaults to a square). An unrecognized shape\n * name falls back to a rectangle rather than throwing: Mermaid adds shape\n * names regularly, and rendering a plain box beats failing the whole diagram\n * over one unknown name.\n */\nfunction expandedNodeShape(meta: ExpandedNodeMeta): NodeShape {\n if (meta.shape) {\n return resolveShapeName(meta.shape) ?? 'rectangle'\n }\n\n if (meta.icon !== undefined || meta.img !== undefined) {\n switch (meta.form?.toLowerCase()) {\n case 'circle':\n return 'circle'\n case 'rounded':\n return 'rounded'\n default:\n return 'rectangle'\n }\n }\n\n return 'rectangle'\n}\n\n/**\n * Resolve the display label for an `A@{ ... }` node.\n *\n * Falls back to the node id when no `label:` is given, matching how the\n * bracket syntax treats a bare `A`. For an icon or image node with no label,\n * the icon/image reference itself is shown: this renderer draws neither\n * FontAwesome glyphs nor remote images, so showing the reference is more\n * useful than an empty box, and it keeps the node identifiable.\n */\nfunction expandedNodeLabel(id: string, meta: ExpandedNodeMeta): string {\n if (meta.label !== undefined && meta.label.length > 0) return meta.label\n if (meta.icon) return meta.icon\n if (meta.img) return meta.img\n return id\n}\n\n/**\n * Apply a `click` statement to the graph.\n *\n * Mermaid's forms:\n * click A \"https://example.com\"\n * click A \"https://example.com\" \"Tooltip\"\n * click A \"https://example.com\" _blank\n * click A href \"https://example.com\" \"Tooltip\" _blank\n * click A call myCallback()\n * click A callback \"Tooltip\"\n *\n * A callback is recorded but never invoked — this renderer emits static SVG\n * and does not execute script supplied by a diagram. An href, by contrast, is\n * genuinely actionable: the node is wrapped in an SVG <a>, which works in any\n * browser without script.\n *\n * The actual grammar and href-safety rules live in packages/core/src/click-directive.ts,\n * shared with the class diagram parser\n * (packages/mermaid-parser/src/class/parser.ts) — this is a thin wrapper\n * binding it to this parser's `graph.interactions` map.\n */\nfunction applyClickStatement(line: string, graph: MermaidGraph): void {\n applyClickStatementShared(line, graph.interactions)\n}\n\n/**\n * Id immediately followed by the expanded-syntax opener on a standalone\n * metadata line: `e1@{ ... }`.\n *\n * Shared by both parsers (`tryApplyEdgeMetaLine`) since the syntax and its\n * \"must already be a known edge id\" disambiguation rule are identical for\n * flowcharts and state diagrams — only how edge ids get declared differs\n * (`A e1@--> B` vs. `A e1@--> B` with a simpler arrow set).\n */\nconst EDGE_META_ID_REGEX = /^([\\w-]+)(?=@\\{)/\n\n/**\n * Try to parse and apply a standalone `e1@{ animate: true }` edge-metadata\n * line.\n *\n * Told apart from a node's `A@{ shape: ... }` by whether the id was already\n * declared as an edge id (`A e1@--> B`) — checked before node parsing so an\n * edge id is never registered as a stray node. Shared between\n * `parseFlowchart` and `parseStateDiagram`; returns `true` if the line was\n * consumed as edge metadata (caller should `continue` its loop).\n */\nfunction tryApplyEdgeMetaLine(line: string, graph: MermaidGraph): boolean {\n const edgeMetaMatch = line.match(EDGE_META_ID_REGEX)\n if (!edgeMetaMatch || !graph.edges.some((e) => e.id === edgeMetaMatch[1])) {\n return false\n }\n const block = matchExpandedBlock(line.slice(edgeMetaMatch[1]!.length))\n if (!block) return false\n applyEdgeMeta(graph, edgeMetaMatch[1]!, parseExpandedMeta(block.body))\n return true\n}\n\n/**\n * Apply an `e1@{ ... }` metadata block to the edge with that id.\n *\n * Only `animate` is acted on; Mermaid's other edge keys (`animation`,\n * `curve`) are recorded as no-ops rather than errors, matching how an\n * unknown node shape degrades.\n */\nfunction applyEdgeMeta(\n graph: MermaidGraph,\n edgeId: string,\n meta: Record<string, string | undefined>,\n): void {\n for (const edge of graph.edges) {\n if (edge.id !== edgeId) continue\n if (meta.animate !== undefined) {\n edge.animate = meta.animate.toLowerCase() !== 'false'\n }\n // Mermaid's `animation: fast|slow` is a speed hint on top of animate.\n if (meta.animation !== undefined) edge.animate = true\n }\n}\n\n/** Regex for ::: class shorthand suffix — matches :::className immediately after a node */\nconst CLASS_SHORTHAND_REGEX = /^:::([\\w][\\w-]*)/\n\n/**\n * Regex for ::: class shorthand appearing BEFORE the shape brackets,\n * e.g. A:::external[Label]. Captures the bare id and the class name so\n * the shorthand can be stripped out before shape-pattern matching runs.\n *\n * The id group allows `\\p{L}` (see BARE_NODE_REGEX above) so a non-ASCII\n * id keeps working with this shorthand instead of falling through to\n * BARE_NODE_REGEX with the `:::className` suffix left dangling as\n * unparsed text. The class-name group stays ASCII-only — Mermaid class\n * names follow CSS identifier conventions, not node-name conventions.\n */\nconst PRE_CLASS_SHORTHAND_REGEX = /^([\\w\\p{L}-]+):::([\\w][\\w-]*)/u\n\n/**\n * Parse a line that contains node definitions and edges.\n * Handles chaining: A --> B --> C produces edges A→B and B→C.\n * Handles parallel links: A & B --> C & D produces 4 edges.\n */\nfunction parseEdgeLine(\n line: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): void {\n let remaining = line.trim()\n\n // Parse the first node group (possibly with & separators)\n const firstGroup = consumeNodeGroup(remaining, graph, subgraphStack)\n if (!firstGroup || firstGroup.ids.length === 0) return\n\n remaining = firstGroup.remaining.trim()\n let prevGroupIds = firstGroup.ids\n\n // Parse arrow + node-group pairs until the line is exhausted\n while (remaining.length > 0) {\n let hasArrowStart: boolean\n let style: EdgeStyle\n let hasArrowEnd: boolean\n let edgeLabel: string | undefined\n let startMarkerKind: 'circle' | 'cross' | undefined\n let endMarkerKind: 'circle' | 'cross' | undefined\n\n /*\n * Optional edge id prefix: `A e1@--> B` (Mermaid v11.10.0+). Consumed\n * before the arrow so ARROW_REGEX still sees the link token at position\n * zero. `@` is not otherwise valid ahead of a link, so this cannot\n * shadow existing syntax.\n */\n let edgeId: string | undefined\n const edgeIdMatch = remaining.match(/^([\\w-]+)@(?=[-=<~ox])/)\n if (edgeIdMatch) {\n edgeId = edgeIdMatch[1]\n remaining = remaining.slice(edgeIdMatch[0].length)\n }\n\n const arrowMatch = remaining.match(ARROW_REGEX)\n const arrowBody = arrowMatch?.[2]\n const startMarker = arrowMatch?.[1]\n const endMarker = arrowMatch?.[3]\n\n if (\n arrowMatch &&\n arrowBody !== undefined &&\n // An unmarked `--`/`==` is the text-label opener, not a link.\n !(\n startMarker === undefined &&\n endMarker === undefined &&\n AMBIGUOUS_UNMARKED_BODIES.has(arrowBody)\n )\n ) {\n // `o`/`x` mark a circle/cross terminator (alongside `<` for a reversed\n // arrow) — `markerKind` records which, so the ASCII renderer can draw\n // a distinct glyph instead of the default arrowhead (see issue #330,\n // a follow-up to issue #65 which first stopped these from being\n // dropped entirely).\n hasArrowStart = startMarker !== undefined\n startMarkerKind = markerKind(startMarker)\n const rawEdgeLabel = arrowMatch[4]?.trim()\n edgeLabel = rawEdgeLabel ? normalizeBrTags(rawEdgeLabel) : undefined\n remaining = remaining.slice(arrowMatch[0].length).trim()\n style = arrowStyleFromBody(arrowBody)\n hasArrowEnd = endMarker !== undefined\n endMarkerKind = markerKind(endMarker)\n } else {\n // Fallback: text-embedded label syntax (-- Yes -->, -. Maybe .->, == Sure ==>)\n const textMatch = remaining.match(TEXT_ARROW_REGEX)\n if (!textMatch) break\n hasArrowStart = Boolean(textMatch[1])\n startMarkerKind = markerKind(textMatch[1])\n const rawLabel = textMatch[3]!.trim()\n edgeLabel = rawLabel ? normalizeBrTags(rawLabel) : undefined\n const openOp = textMatch[2]!\n const closeOp = textMatch[4]!\n remaining = remaining.slice(textMatch[0].length).trim()\n style = textArrowStyleFromOps(openOp, closeOp)\n // closeOp is a variable-length run (e.g. \"---o\", \"==x\", \"-.-.->\"); only\n // its final character can be a circle/cross marker. A circle/cross\n // terminator is its own kind of arrow ending — like `>` — so it counts\n // toward hasArrowEnd too; an unmarked run (`---`, `===`, `-.-`) is the\n // only case with no arrowhead of any kind at the close.\n endMarkerKind = markerKind(closeOp.slice(-1))\n hasArrowEnd = closeOp.endsWith('>') || endMarkerKind !== undefined\n }\n\n // Parse the next node group\n const nextGroup = consumeNodeGroup(remaining, graph, subgraphStack)\n if (!nextGroup || nextGroup.ids.length === 0) break\n\n remaining = nextGroup.remaining.trim()\n\n // Emit Cartesian product of edges: every source × every target\n for (const sourceId of prevGroupIds) {\n for (const targetId of nextGroup.ids) {\n graph.edges.push({\n source: sourceId,\n target: targetId,\n label: edgeLabel,\n style,\n hasArrowStart,\n hasArrowEnd,\n ...(startMarkerKind !== undefined\n ? { startMarker: startMarkerKind }\n : {}),\n ...(endMarkerKind !== undefined ? { endMarker: endMarkerKind } : {}),\n ...(edgeId !== undefined ? { id: edgeId } : {}),\n })\n }\n }\n\n prevGroupIds = nextGroup.ids\n }\n}\n\ninterface ConsumedNodeGroup {\n ids: string[]\n remaining: string\n}\n\n/**\n * Consume one or more nodes separated by `&`.\n * E.g. \"A & B & C --> ...\" returns ids: ['A', 'B', 'C']\n */\nfunction consumeNodeGroup(\n text: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): ConsumedNodeGroup | null {\n const first = consumeNode(text, graph, subgraphStack)\n if (!first) return null\n\n const ids = [first.id]\n let remaining = first.remaining.trim()\n\n // Check for & separators\n while (remaining.startsWith('&')) {\n remaining = remaining.slice(1).trim()\n const next = consumeNode(remaining, graph, subgraphStack)\n if (!next) break\n ids.push(next.id)\n remaining = next.remaining.trim()\n }\n\n return { ids, remaining }\n}\n\ninterface ConsumedNode {\n id: string\n remaining: string\n}\n\n/**\n * Try to consume a node definition from the start of `text`.\n * If the node has a shape+label (e.g. A[Text]), it's registered in the graph.\n * If it's a bare reference (e.g. A), we look it up or create a default.\n * Also handles ::: class shorthand suffix.\n */\nfunction consumeNode(\n text: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): ConsumedNode | null {\n let id: string | null = null\n let remaining: string = text\n\n // Check for ::: class shorthand appearing BEFORE the shape brackets\n // (e.g. A:::external[Label]). Strip it out so shape-pattern matching\n // below still sees the id directly adjacent to its brackets.\n let preClassName: string | undefined\n const preClassMatch = remaining.match(PRE_CLASS_SHORTHAND_REGEX)\n if (preClassMatch) {\n preClassName = preClassMatch[2]!\n remaining = preClassMatch[1]! + remaining.slice(preClassMatch[0].length)\n }\n\n /*\n * Expanded syntax: `A@{ shape: doc, label: \"Report\" }` (Mermaid v11.3.0+).\n *\n * Tried before the bracket patterns because the id is followed by `@{`,\n * which none of them match — without this the id would fall through to\n * BARE_NODE_REGEX and the whole metadata block would be stranded as\n * unparsed text.\n */\n const expandedIdMatch = remaining.match(EXPANDED_NODE_ID_REGEX)\n if (expandedIdMatch) {\n const expandedId = expandedIdMatch[1]!\n const block = matchExpandedBlock(remaining.slice(expandedId.length))\n if (block) {\n const meta = parseExpandedMeta(block.body)\n registerNode(graph, subgraphStack, {\n id: expandedId,\n label: normalizeBrTags(expandedNodeLabel(expandedId, meta)),\n shape: expandedNodeShape(meta),\n })\n id = expandedId\n remaining = remaining.slice(expandedId.length + block.length)\n }\n }\n\n // Try each node pattern (shape-qualified)\n if (id === null) {\n for (const { regex, shape } of NODE_PATTERNS) {\n const match = remaining.match(regex)\n if (match) {\n id = match[1]!\n const label = normalizeBrTags(match[2]!)\n // The slash-bracket family resolves its shape from the closing\n // delimiter it captured in group 3; every other pattern has a fixed\n // shape. See the SLASH_BRACKET note in NODE_PATTERNS.\n const resolvedShape =\n typeof shape === 'function' ? shape(match[3] ?? '') : shape\n registerNode(graph, subgraphStack, { id, label, shape: resolvedShape })\n remaining = remaining.slice(match[0].length)\n break\n }\n }\n }\n\n // Bare node reference — only register if node doesn't exist yet.\n // If it already exists, do NOT track it in the current subgraph;\n // nodes belong to the subgraph where they're first defined.\n if (id === null) {\n const bareMatch = remaining.match(BARE_NODE_REGEX)\n if (bareMatch) {\n id = bareMatch[1]!\n if (!graph.nodes.has(id)) {\n registerNode(graph, subgraphStack, {\n id,\n label: id,\n shape: 'rectangle',\n })\n }\n remaining = remaining.slice(bareMatch[0].length)\n }\n }\n\n if (id === null) return null\n\n if (preClassName) {\n graph.classAssignments.set(id, preClassName)\n }\n\n // Check for ::: class shorthand suffix immediately after the node\n const classMatch = remaining.match(CLASS_SHORTHAND_REGEX)\n if (classMatch) {\n graph.classAssignments.set(id, classMatch[1]!)\n remaining = remaining.slice(classMatch[0].length)\n }\n\n return { id, remaining }\n}\n\n/** Register a node in the graph and track it in the current subgraph */\nfunction registerNode(\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n node: MermaidNode,\n): void {\n const isNew = !graph.nodes.has(node.id)\n if (isNew) {\n graph.nodes.set(node.id, node)\n }\n trackInSubgraph(subgraphStack, node.id)\n}\n\n/** Add node ID to the innermost subgraph if we're inside one */\nfunction trackInSubgraph(\n subgraphStack: MermaidSubgraph[],\n nodeId: string,\n): void {\n if (subgraphStack.length > 0) {\n const current = subgraphStack[subgraphStack.length - 1]!\n if (!current.nodeIds.includes(nodeId)) {\n current.nodeIds.push(nodeId)\n }\n }\n}\n\n/**\n * Map a link body to its edge style, ignoring direction and run length.\n *\n * The body is the run between the optional start/end markers: `--`, `----`,\n * `==`, `-.-`, `-..-`, `~~~`, etc. Classification is by the characters used,\n * not the length, so every run length of a given style behaves identically.\n */\nfunction arrowStyleFromBody(body: string): EdgeStyle {\n if (body.startsWith('~')) return 'invisible'\n if (body.includes('.')) return 'dotted'\n if (body.startsWith('=')) return 'thick'\n return 'solid'\n}\n\n/** Map text-embedded arrow open/close operators to edge style */\nfunction textArrowStyleFromOps(openOp: string, closeOp: string): EdgeStyle {\n // Classify by the characters used, not by exact token, so every run length\n // of a given style resolves identically — the same rule arrowStyleFromBody\n // applies to the plain arrow forms.\n if (openOp.includes('.') || closeOp.includes('.')) return 'dotted'\n if (openOp.startsWith('=') || closeOp.startsWith('=')) return 'thick'\n return 'solid'\n}\n","// ============================================================================\n// zombie-mermaid — SVG diagram-type registry\n//\n// A per-type registration table `renderMermaidSVG` (./index.ts) looks up\n// instead of hand-maintaining its own `switch (diagramType)` over the same\n// `DiagramType`. Moved here from the umbrella's `src/diagram-registry.ts`\n// under issue #1111, for parity with `@zombie-mermaid/ascii-renderer`'s own\n// `registry.ts` — see docs/decisions/diagram-type-registry-partial.md for\n// why the SVG and ASCII halves stayed two separate tables rather than one\n// shared `DiagramModule` with both a `renderSvg` and a `renderAscii` method\n// (short version: one table made this module import out of\n// `packages/ascii-renderer/`, while that package's `index.ts` imported this\n// one back, a cycle that blocked the monorepo split and dragged `elkjs`\n// into `dist/ascii.js`).\n//\n// Every diagram type is registered here, including 'flowchart' — see the\n// decision doc for why it was the last holdout (its ASCII path had no\n// per-type wrapper function to slot in until #745 extracted\n// `renderFlowchartAscii`, and its SVG path carries `%%{init: ...}%%`\n// directive handling no other type has — see `flowchartModule.layoutForSvg`\n// below for how that's folded in without growing `SvgRenderContext`). The\n// front door (`renderMermaidSVG` in ./index.ts) has no fallback switch left;\n// every `DiagramType` is a hit here.\n//\n// `packages/core/src/diagram-type.ts` (the `DiagramType` union + `detectDiagramType`)\n// stays exactly as-is and is what the front door uses to key into this\n// table — this module doesn't touch detection.\n//\n// `parseMermaid` (flowchart/state parsing) reaches out to the umbrella's\n// `../../../src/parser.ts` rather than a `@zombie-mermaid/*` package\n// specifier — flowchart/state parsing was deliberately never extracted into\n// `@zombie-mermaid/mermaid-parser` (see that package's scoping doc), the same\n// pre-existing boundary call `packages/ascii-renderer/src/flowchart.ts`\n// already reaches across for the ASCII side. `vite.config.ts` bundles that\n// file straight into this package's own dist, exactly as it already does\n// for `ascii-renderer` — see that file's header comment.\n// ============================================================================\n\nimport type {\n DiagramType,\n RenderOptions,\n DiagramColors,\n SvgEmitOptions,\n MermaidGraph,\n PositionedGraph,\n CurveStyle,\n Statement,\n} from '@zombie-mermaid/core'\nimport type { FontSizes } from './styles.ts'\nimport { withDirectionOverride } from '@zombie-mermaid/core'\n\nimport {\n parseXYChart,\n parseErDiagram,\n parseSequenceDiagram,\n parseClassDiagram,\n} from '@zombie-mermaid/mermaid-parser'\nimport type {\n XYChart,\n PositionedXYChart,\n ErDiagram,\n PositionedErDiagram,\n SequenceDiagram,\n PositionedSequenceDiagram,\n ClassDiagram,\n PositionedClassDiagram,\n} from '@zombie-mermaid/mermaid-parser'\nimport { layoutXYChart } from './xychart/layout.ts'\nimport { renderXYChartSvg } from './xychart/renderer.ts'\nimport { layoutErDiagramSync } from './er/layout.ts'\nimport { renderErSvg } from './er/renderer.ts'\nimport { layoutSequenceDiagram } from './sequence/layout.ts'\nimport { renderSequenceSvg } from './sequence/renderer.ts'\nimport { layoutClassDiagramSync } from './class/layout.ts'\nimport { renderClassSvg } from './class/renderer.ts'\nimport { layoutGraphSync } from './layout-engine.ts'\nimport { renderSvg as renderFlowchartSvg } from './renderer.ts'\nimport { parseMermaid } from '../../../src/parser.ts'\n\n/**\n * Parameters shared by every per-type SVG renderer today, factored out of\n * the positional-argument lists that otherwise differ type to type (the\n * inconsistency issue #533 flags — confirmed by reading all five renderer\n * signatures: `embedSource`/`title`/`decorative`/`emit` shift position\n * across `renderSvg`, `renderSequenceSvg`, `renderClassSvg`, `renderErSvg`,\n * `renderXYChartSvg`, and `renderErSvg` drops `linksEnabled` entirely).\n *\n * Fields only one or two types need (`curve`, `animationEnabled`,\n * `linksEnabled`, `interactive`) are deliberately left OUT of this shared\n * shape rather than grown in for every exception — each type's own\n * `renderSvg` adapter below derives those straight from `options`, exactly\n * as each existing `renderMermaidSVGRaw` switch case already did.\n */\nexport interface SvgRenderContext {\n colors: DiagramColors\n font: string\n transparent: boolean\n fontSizes: FontSizes\n embedSource?: string\n title?: string\n decorative?: boolean\n emit: SvgEmitOptions\n}\n\n/**\n * One diagram type's full registration.\n *\n * `layoutForSvg` is deliberately SVG-only — it is NOT shared with the ASCII\n * side, unlike the issue's original `{ detect, parse, layout, renderSvg,\n * renderAscii }` sketch. Reading every ASCII per-type module confirmed why a\n * single shared `layout` step would be fiction, not simplification, for this\n * codebase: SVG layout produces pixel coordinates (`PositionedXYChart`,\n * `PositionedErDiagram`, …), while every ASCII renderer does its own,\n * unrelated grid/canvas layout internally — see e.g.\n * `packages/ascii-renderer/src/xychart.ts`'s file header: \"Uses the parsed\n * XYChart type directly (not PositionedXYChart) since pixel coordinates\n * don't map to character grids.\" `parse` genuinely is shared in the sense\n * that both front doors call the same `parseXYChart`/`parseErDiagram`, but\n * each ASCII renderer reruns that parse itself from raw text — which is why\n * the ASCII entries live in `packages/ascii-renderer/src/registry.ts` as\n * plain `(text, …) => string` functions instead of a `renderAscii` method on\n * this interface.\n *\n * `parse` takes both `lines` (the pre-split statement list from\n * `splitStatements(decoded)`, computed once by the front door — what every\n * already-registered type's parser wants) and `text` (the raw, un-split\n * source `flowchartModule` below needs instead): `parseMermaid`'s\n * `%%{init: ...}%%` directive extraction reads raw, un-commented lines that\n * `splitStatements` has already discarded by the time `lines` exists, and\n * its multi-line-statement continuation merging needs each statement's\n * *originating physical line* grouping, which `splitStatements`'s flattened\n * array has already lost. `xychartModule`/`erModule`/`sequenceModule`/\n * `classModule` all ignore the second parameter — JS/TS functions may take\n * fewer parameters than their declared type allows, so `parse: parseXYChart`\n * (etc.) is unchanged from before this parameter was added.\n */\nexport interface DiagramModule<TDiagram = unknown, TPositioned = unknown> {\n readonly type: DiagramType\n parse(lines: Statement[], text: string): TDiagram\n layoutForSvg(diagram: TDiagram, options: RenderOptions): TPositioned\n renderSvg(\n positioned: TPositioned,\n ctx: SvgRenderContext,\n options: RenderOptions,\n ): string\n}\n\nconst xychartModule: DiagramModule<XYChart, PositionedXYChart> = {\n type: 'xychart',\n parse: parseXYChart,\n layoutForSvg: layoutXYChart,\n renderSvg(positioned, ctx, options) {\n // Mirrors resolveXYChartInteractive() in the umbrella's src/index.ts\n // exactly — `interactivity` wins when set, otherwise the deprecated\n // `interactive` boolean keeps controlling this as before.\n const interactive =\n options.interactivity !== undefined\n ? options.interactivity === 'full'\n : (options.interactive ?? false)\n return renderXYChartSvg(\n positioned,\n ctx.colors,\n ctx.font,\n ctx.transparent,\n interactive,\n ctx.embedSource,\n ctx.title,\n ctx.decorative,\n ctx.emit,\n )\n },\n}\n\nconst sequenceModule: DiagramModule<\n SequenceDiagram,\n PositionedSequenceDiagram\n> = {\n type: 'sequence',\n parse: parseSequenceDiagram,\n layoutForSvg: layoutSequenceDiagram,\n renderSvg(positioned, ctx) {\n return renderSequenceSvg(\n positioned,\n ctx.colors,\n ctx.font,\n ctx.transparent,\n ctx.fontSizes,\n ctx.embedSource,\n ctx.title,\n ctx.decorative,\n ctx.emit,\n )\n },\n}\n\n/**\n * Mirrors `resolveLinksEnabled()` in the umbrella's src/index.ts exactly\n * (`interactivity` defaults unset to `'static'`; only `'none'` turns links\n * off) — duplicated here rather than imported since that helper is private\n * to src/index.ts, which now imports this module (importing it back would\n * cycle).\n */\nfunction resolveLinksEnabled(options: RenderOptions): boolean {\n const interactivity = options.interactivity ?? 'static'\n return interactivity !== 'none'\n}\n\n/**\n * Mirrors `resolveAnimationEnabled()` in the umbrella's src/index.ts exactly\n * (`interactivity` defaults unset to `'static'`; only `'full'` turns\n * animation on) — duplicated here for the same import-direction reason\n * `resolveLinksEnabled` above is.\n */\nfunction resolveAnimationEnabled(options: RenderOptions): boolean {\n const interactivity = options.interactivity ?? 'static'\n return interactivity === 'full'\n}\n\nconst classModule: DiagramModule<ClassDiagram, PositionedClassDiagram> = {\n type: 'class',\n parse: parseClassDiagram,\n layoutForSvg: layoutClassDiagramSync,\n renderSvg(positioned, ctx, options) {\n return renderClassSvg(\n positioned,\n ctx.colors,\n ctx.font,\n ctx.transparent,\n ctx.fontSizes,\n ctx.embedSource,\n ctx.title,\n ctx.decorative,\n resolveLinksEnabled(options),\n ctx.emit,\n )\n },\n}\n\nconst erModule: DiagramModule<ErDiagram, PositionedErDiagram> = {\n type: 'er',\n parse: parseErDiagram,\n // `options.direction` replaces the diagram's own top-level `direction`\n // line, if any, before layout — same order as the umbrella's original\n // 'er' case: parse, then withDirectionOverride, then layout.\n layoutForSvg(diagram, options) {\n return layoutErDiagramSync(\n withDirectionOverride(diagram, options.direction),\n options,\n )\n },\n renderSvg(positioned, ctx) {\n return renderErSvg(\n positioned,\n ctx.colors,\n ctx.font,\n ctx.transparent,\n ctx.fontSizes,\n ctx.embedSource,\n ctx.title,\n ctx.decorative,\n ctx.emit,\n )\n },\n}\n\n/**\n * `renderSvg`'s (the low-level flowchart/state SVG emitter, imported above\n * as `renderFlowchartSvg`) `curve` parameter is the one piece of\n * `SvgRenderContext`-adjacent state no other registered type needs: a\n * `%%{init: {\"flowchart\": {\"curve\": ...}}}%%` directive on the diagram\n * itself can supply a default, and `options.curve` always wins when set —\n * see `applyInitConfig()` in `packages/core/src/init-directive.ts`, which\n * the umbrella's pre-registry flowchart path used to call directly.\n *\n * That resolution needs the parsed diagram (for `initConfig`) AND the\n * caller's `options` together, and the *result* is only needed later, by\n * `renderSvg` — so rather than teach `SvgRenderContext` a diagram-specific\n * `curve` field (every other field there is genuinely shared across types),\n * `layoutForSvg` below resolves it once and carries it forward on\n * `PositionedFlowchart`, right next to the `PositionedGraph` it was\n * resolved alongside. `animationEnabled`/`linksEnabled` need no such\n * carry-through: both derive from `options` alone via the\n * `resolveAnimationEnabled`/`resolveLinksEnabled` helpers above, exactly\n * like `class`'s `linksEnabled` already does, so `flowchartModule.renderSvg`\n * just calls them directly.\n */\nexport interface PositionedFlowchart {\n graph: PositionedGraph\n curve: CurveStyle\n}\n\nconst flowchartModule: DiagramModule<MermaidGraph, PositionedFlowchart> = {\n type: 'flowchart',\n // Ignores `lines` — see the `parse` doc comment on `DiagramModule` above\n // for why flowchart/state needs the raw `text` instead.\n parse: (_lines, text) => parseMermaid(text),\n layoutForSvg(diagram, options) {\n // Same order as the umbrella's original fallback: direction override,\n // then layout. `options.curve` wins over the diagram's own\n // `%%{init: ...}%%` directive, which wins over the 'linear' default —\n // see the doc comment on `PositionedFlowchart` above.\n const graph = withDirectionOverride(diagram, options.direction)\n return {\n graph: layoutGraphSync(graph, options),\n curve: options.curve ?? diagram.initConfig?.curve ?? 'linear',\n }\n },\n renderSvg(positioned, ctx, options) {\n return renderFlowchartSvg(\n positioned.graph,\n ctx.colors,\n ctx.font,\n ctx.transparent,\n ctx.fontSizes,\n positioned.curve,\n ctx.embedSource,\n resolveAnimationEnabled(options),\n resolveLinksEnabled(options),\n ctx.title,\n ctx.decorative,\n ctx.emit,\n )\n },\n}\n\n/**\n * The registry proper — every `DiagramType` is looked up here by the SVG\n * front door (`renderMermaidSVG` in ./index.ts), which has no fallback\n * switch left. The ASCII front door's equivalent table is `asciiRegistry`\n * in `packages/ascii-renderer/src/registry.ts`.\n *\n * Typed with `any` type parameters at the map level: each entry's own\n * `TDiagram`/`TPositioned` are only known inside that entry's own closure\n * (see `xychartModule`/`erModule`/`flowchartModule` above, which are fully\n * typed); the map just needs one consistent shape to hold heterogeneous\n * entries in.\n */\ntype AnyDiagramModule = DiagramModule<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see comment above; each entry is fully typed at its own definition site.\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see comment above; each entry is fully typed at its own definition site.\n any\n>\n\nexport const diagramRegistry: Record<DiagramType, AnyDiagramModule> = {\n xychart: xychartModule,\n er: erModule,\n sequence: sequenceModule,\n class: classModule,\n flowchart: flowchartModule,\n}\n","// ============================================================================\n// @zombie-mermaid/svg-renderer — SVG emission and ELK-backed layout\n//\n// Every file re-exported below was grep-verified (zombie-mermaid#625,\n// umbrella #620) as unreachable from `src/ascii/index.ts` except through\n// the SVG-side modules the umbrella still owns — so nothing here can put\n// `elkjs` on the ASCII renderer's dependency path once #623 extracts it.\n//\n// This package depends on `@zombie-mermaid/core` and, since #624, on\n// `@zombie-mermaid/mermaid-parser` too — an ordinary, acyclic dependency\n// (both `svg-renderer` and the future `ascii-renderer` depend on\n// `mermaid-parser` directly, per the scoping doc's finding 2), not a cycle:\n// every per-type `layout.ts`/`renderer.ts` below needs the positioned-\n// diagram types `mermaid-parser`'s `types.ts` halves own, and\n// `class/layout.ts` needs `formatClassMember` from `mermaid-parser`'s\n// `class/format.ts`. It deliberately does NOT re-export the internal\n// `layout-engine/` modules the umbrella never imports\n// (`constants.ts` — whose `DEFAULTS` would collide with `core`'s theme\n// `DEFAULTS` anyway — plus `edge-bundling.ts`, `from-elk.ts`,\n// `layer-alignment.ts` and `to-elk.ts`): they are `layoutGraphSync()`'s\n// implementation, not its API. `elk-graph-builder.ts` is the exception:\n// `class/layout.ts` and `er/layout.ts` (moved into this package under\n// #624) call its primitives directly (zombie-mermaid#616), so it is part\n// of the public API alongside `elk-adapter-utils.ts`.\n//\n// `layout.ts` used to be a thin re-export of `layoutGraphSync` from\n// `layout-engine.ts`; it was folded into `layout-engine.ts` directly\n// (#1109), so `layoutGraphSync` now comes from the one star-export below.\n//\n// `class/`, `er/`, `sequence/`, `xychart/` hold each diagram type's\n// renderer half (`layout.ts` + `renderer.ts`) — the other half\n// (`parser.ts`/`types.ts`, plus `class/format.ts`, `sequence/box-color.ts`,\n// `sequence/activation-check.ts`, `xychart/colors.ts`) moved to\n// `@zombie-mermaid/mermaid-parser` under the same issue (#624), per the\n// scoping doc's finding 1: each per-type directory used to mix both halves\n// in one place, and the split point already existed at the file level.\n//\n// `renderMermaidSVG` (below) is the single \"Mermaid text in, SVG out\" front\n// door — moved here from the umbrella's `src/index.ts` under issue #1111,\n// for parity with `@zombie-mermaid/ascii-renderer`'s `renderMermaidASCII`.\n// `./registry.ts` (not re-exported — same as ascii-renderer's own\n// `registry.ts` — it's this front door's internal dispatch table, not part\n// of the public API) drives per-diagram-type dispatch; see that file's\n// header for why it's a separate module from the ASCII side's table.\n// ============================================================================\n\nexport * from './edge-curves.ts'\nexport * from './elk-instance.ts'\nexport * from './layout-engine.ts'\nexport * from './layout-engine/elk-adapter-utils.ts'\nexport * from './layout-engine/elk-graph-builder.ts'\nexport * from './renderer.ts'\nexport * from './resolve-colors.ts'\nexport * from './shape-clipping.ts'\nexport * from './styles.ts'\n\nexport * from './class/layout.ts'\nexport * from './class/renderer.ts'\nexport * from './er/layout.ts'\nexport * from './er/renderer.ts'\nexport * from './sequence/layout.ts'\nexport * from './sequence/renderer.ts'\nexport * from './xychart/layout.ts'\nexport * from './xychart/renderer.ts'\n\nimport { decodeXML } from 'entities'\nimport type {\n RenderOptions,\n DiagramColors,\n SvgEmitOptions,\n DiagramType,\n} from '@zombie-mermaid/core'\nimport {\n DEFAULTS,\n themeStyleDeclarations,\n isMonospaceFont,\n setMonospaceMetrics,\n detectDiagramType,\n splitStatements,\n} from '@zombie-mermaid/core'\nimport { resolveCssColors } from './resolve-colors.ts'\nimport { resolveFontSizes } from './styles.ts'\nimport { diagramRegistry } from './registry.ts'\nimport type { SvgRenderContext } from './registry.ts'\n\n/**\n * Build a DiagramColors object from render options.\n * Uses DEFAULTS for bg/fg when not provided, and passes through\n * optional enrichment colors (line, accent, muted, surface, border).\n */\nfunction buildColors(options: RenderOptions): DiagramColors {\n return {\n bg: options.bg ?? DEFAULTS.bg,\n fg: options.fg ?? DEFAULTS.fg,\n line: options.line,\n accent: options.accent,\n muted: options.muted,\n surface: options.surface,\n border: options.border,\n }\n}\n\n/**\n * The exact CSS declaration list the root `<svg style=\"…\">` attribute would\n * carry for these options — `--bg`, `--fg`, whichever enrichment colours\n * were given, and (unless `transparent`) `background: var(--bg)`.\n *\n * For hosts with a strict `Content-Security-Policy`: a `style=` attribute\n * can't be nonced, so a `style-src` without `'unsafe-inline'` drops it and\n * the diagram loses its colours. Render with `styleAttribute: false` and\n * put this string in your own stylesheet on the SVG (or any ancestor —\n * custom properties inherit) instead. Pass the same options object to both\n * calls so the declarations match what the render expects. See\n * `RenderOptions.styleAttribute` / `RenderOptions.nonce` and issue #216.\n *\n * Built by the same function that fills the attribute in normal renders,\n * so there is one variable list to keep in sync. The string is compact\n * (`--bg:#fff;--fg:#000;background:var(--bg)`) — valid inside any rule\n * block — and the colour values are yours, unescaped, exactly as the\n * attribute has always carried them.\n *\n * @example\n * ```ts\n * const opts = { bg: '#1a1b26', fg: '#a9b1d6', nonce, styleAttribute: false }\n * const svg = renderMermaidSVG('graph TD\\n A --> B', opts)\n * const css = `.diagram svg { ${themeCssVariables(opts)} }`\n * ```\n */\nexport function themeCssVariables(options: RenderOptions = {}): string {\n return themeStyleDeclarations(buildColors(options), options.transparent)\n}\n\n/**\n * Resolve the effective strict-CSP emission controls from the public\n * options. Kept as one object so every renderer takes it as a single\n * trailing parameter — see `SvgEmitOptions` in packages/core/src/theme.ts.\n */\nfunction resolveSvgEmit(options: RenderOptions): SvgEmitOptions {\n return {\n nonce: options.nonce,\n styleAttribute: options.styleAttribute,\n }\n}\n\n// Interactivity-derived render gates — whether xychart hover tooltips\n// render, whether flowchart/state edge animation (`e1@{ animate: true }`)\n// plays, and whether `click`-based links/`<title>` tooltips render — all\n// now live next to their diagram type's registry entry in ./registry.ts\n// (`resolveAnimationEnabled`/`resolveLinksEnabled` there, duplicated rather\n// than imported since that module is imported BY this file — see those\n// functions' own comments for why), since every diagram type's SVG dispatch\n// is fully handled by the registry lookup below and there is no remaining\n// switch case here for them to serve.\n\n/**\n * Render Mermaid diagram text to an SVG string — synchronously.\n *\n * Uses elk.bundled.js with a direct FakeWorker bypass (no setTimeout(0) delay).\n * The ELK singleton is created lazily on first use and cached forever.\n *\n * Use this in React components with useMemo() to avoid flash:\n * const svg = useMemo(() => renderMermaidSVG(code, opts), [code])\n *\n * @param text - Mermaid source text\n * @param options - Rendering options (colors, font, spacing)\n * @returns A self-contained SVG string\n *\n * @example\n * ```ts\n * const svg = renderMermaidSVG('graph TD\\n A --> B')\n *\n * // With theme\n * const svg = renderMermaidSVG('graph TD\\n A --> B', {\n * bg: '#1a1b26', fg: '#a9b1d6'\n * })\n *\n * // With CSS variables (for live theme switching)\n * const svg = renderMermaidSVG('graph TD\\n A --> B', {\n * bg: 'var(--background)', fg: 'var(--foreground)', transparent: true\n * })\n *\n * // With the original source stamped onto the root <svg> as data-src —\n * // handy for a \"copy source\" button or an \"open in Mermaid Live\" link\n * // without re-attaching it via string surgery on the output.\n * const svg = renderMermaidSVG('graph TD\\n A --> B', { embedSource: true })\n *\n * // With an accessible name — role=\"img\" + aria-labelledby pointing at a\n * // <title> child, so assistive tech announces the diagram instead of\n * // reading every node label individually (see issue #215).\n * const svg = renderMermaidSVG('graph TD\\n A --> B', {\n * title: 'Flowchart: Build → Test → Ship'\n * })\n *\n * // Decorative diagram — already described in surrounding prose, so it's\n * // hidden from assistive tech (aria-hidden=\"true\") instead of named.\n * const svg = renderMermaidSVG('graph TD\\n A --> B', { decorative: true })\n * ```\n */\nexport function renderMermaidSVG(\n text: string,\n options: RenderOptions = {},\n): string {\n const svg = renderMermaidSVGRaw(text, options)\n return options.resolveColors\n ? resolveCssColors(svg, buildColors(options))\n : svg\n}\n\n/** The renderer proper — `renderMermaidSVG` minus the optional `resolveColors` post-pass. */\nfunction renderMermaidSVGRaw(text: string, options: RenderOptions): string {\n // Decode XML entities that may leak from markdown parsers (e.g. rehype-raw).\n // Without this, escapeXml() double-encodes them: &lt; → &amp;lt; → literal \"&lt;\" in SVG.\n // `text` itself is left untouched so `embedSource` below stamps the exact\n // string the caller passed in, not this entity-decoded copy used\n // internally for parsing.\n const decoded = decodeXML(text)\n\n const colors = buildColors(options)\n const font = options.font ?? 'Inter'\n // Box sizing depends on the metrics model, so pick it before any layout runs.\n setMonospaceMetrics(isMonospaceFont(font))\n const transparent = options.transparent ?? false\n const fontSizes = resolveFontSizes(options.fontSizes)\n const diagramType: DiagramType = detectDiagramType(decoded)\n const embedSource = options.embedSource ? text : undefined\n const title = options.title\n const decorative = options.decorative\n const emit = resolveSvgEmit(options)\n\n const lines = splitStatements(decoded)\n\n // Registry dispatch (see ./registry.ts): every diagram type, including\n // 'flowchart' (and the 'state' pipeline it shares), is registered there.\n // `parse` takes both `lines` (what every already-registered type's parser\n // wants) and `decoded` (the raw text flowchart/state's parser needs\n // instead — see the `parse` doc comment on `DiagramModule` in\n // ./registry.ts).\n const registered = diagramRegistry[diagramType]\n const diagram = registered.parse(lines, decoded)\n const positioned = registered.layoutForSvg(diagram, options)\n const ctx: SvgRenderContext = {\n colors,\n font,\n transparent,\n fontSizes,\n embedSource,\n title,\n decorative,\n emit,\n }\n return registered.renderSvg(positioned, ctx, options)\n}\n\n/**\n * Render Mermaid diagram text to an SVG string — async.\n *\n * Same result as renderMermaidSVG() but returns a Promise.\n * Useful in async contexts (server handlers, data loaders, etc.)\n */\nexport async function renderMermaidSVGAsync(\n text: string,\n options: RenderOptions = {},\n): Promise<string> {\n return renderMermaidSVG(text, options)\n}\n\n// ---------------------------------------------------------------------------\n// Backward-compatible aliases\n// ---------------------------------------------------------------------------\n\n/** @deprecated Use `renderMermaidSVG` */\nexport const renderMermaidSync = renderMermaidSVG\n\n/** @deprecated Use `renderMermaidSVGAsync` */\nexport const renderMermaid = renderMermaidSVGAsync\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/edge-curves.ts","../src/elk-instance.ts","../src/styles.ts","../src/layout-engine/constants.ts","../src/layout-engine/elk-graph-builder.ts","../src/layout-engine/to-elk.ts","../src/shape-clipping.ts","../src/layout-engine/layer-alignment.ts","../src/layout-engine/edge-bundling.ts","../src/layout-engine/elk-adapter-utils.ts","../src/layout-engine/from-elk.ts","../src/layout-engine.ts","../src/renderer.ts","../src/resolve-colors.ts","../src/class/layout.ts","../src/class/renderer.ts","../src/er/layout.ts","../src/er/renderer.ts","../src/sequence/layout.ts","../src/sequence/renderer.ts","../src/xychart/layout.ts","../src/xychart/renderer.ts","../src/c4/metrics.ts","../src/c4/layout.ts","../src/c4/renderer.ts","../../../src/parser.ts","../src/registry.ts","../src/index.ts"],"sourcesContent":["// ============================================================================\n// zombie-mermaid — edge path interpolation (Mermaid's `flowchart.curve`)\n//\n// ELK routes an edge as a list of bend points. How those points are joined\n// into a drawn line is a separate, purely presentational choice, which\n// Mermaid exposes as `%%{init: {\"flowchart\": {\"curve\": \"basis\"}}}%%`.\n//\n// Every curve here consumes the same routed points, so switching styles never\n// changes where an edge goes — only how it looks getting there.\n// ============================================================================\n\nimport type { Point, CurveStyle } from '@zombie-mermaid/core'\nimport { f } from '@zombie-mermaid/core'\n\n/** `M x y` for the first point. */\nfunction moveTo(p: Point): string {\n return f`M ${p.x} ${p.y}`\n}\n\n/** Straight segments through every point — the historical default. */\nfunction linearPath(points: Point[]): string {\n return (\n moveTo(points[0]!) +\n points\n .slice(1)\n .map((p) => f` L ${p.x} ${p.y}`)\n .join('')\n )\n}\n\n/**\n * Uniform cubic B-spline, matching d3's `curveBasis` (what Mermaid uses for\n * `basis`).\n *\n * A B-spline is *approximating*, not interpolating: it passes through neither\n * the interior points nor, without the endpoint handling below, the ends. The\n * first and last points are therefore emitted explicitly so an edge still\n * touches the nodes it connects — otherwise it would visibly detach.\n *\n * This is a direct port of d3's `Basis` curve rather than a lookalike, so a\n * diagram rendered here traces the same path Mermaid would draw. For each\n * point d3 emits one cubic whose controls are the thirds between the two\n * preceding points and whose end is the B-spline knot `(p0 + 4p1 + p2) / 6`;\n * it leads in at `(5p0 + p1) / 6` and closes with a final cubic against a\n * repeated last point, then a line to it.\n */\nfunction basisPath(points: Point[]): string {\n if (points.length < 3) return linearPath(points)\n\n const last = points[points.length - 1]!\n const parts: string[] = [moveTo(points[0]!)]\n\n /** One B-spline segment across three successive control points. */\n const segment = (p0: Point, p1: Point, p2: Point): string =>\n f` C ${(2 * p0.x + p1.x) / 3} ${(2 * p0.y + p1.y) / 3},` +\n f` ${(p0.x + 2 * p1.x) / 3} ${(p0.y + 2 * p1.y) / 3},` +\n f` ${(p0.x + 4 * p1.x + p2.x) / 6} ${(p0.y + 4 * p1.y + p2.y) / 6}`\n\n // Lead-in to the first knot, one sixth of the way along the opening leg.\n parts.push(\n f` L ${(5 * points[0]!.x + points[1]!.x) / 6}` +\n f` ${(5 * points[0]!.y + points[1]!.y) / 6}`,\n )\n\n for (let i = 2; i < points.length; i++) {\n parts.push(segment(points[i - 2]!, points[i - 1]!, points[i]!))\n }\n\n // d3 closes by feeding the last point twice, then drawing to it.\n parts.push(segment(points[points.length - 2]!, last, last))\n parts.push(f` L ${last.x} ${last.y}`)\n\n return parts.join('')\n}\n\n/**\n * Straight runs joined by rounded corners. Mermaid calls this `natural`.\n *\n * NOTE: this is deliberately *not* d3's interpolating natural cubic spline.\n * ELK routes edges orthogonally, so every bend is a right angle, and at a\n * right angle the tangent of any C1-smooth interpolating spline is the\n * average of the two segment directions — i.e. diagonal. The curve must then\n * arrive at the corner travelling diagonally while approaching from directly\n * above, so it bulges sideways past the straight line. Two mirrored edges\n * leaving one decision node turned that bulge into a visible teardrop loop.\n * Scaling the tangents down shrinks the overshoot but cannot remove it; it is\n * a property of interpolating a 90° corner smoothly.\n *\n * Rounding the corners instead gives a smooth path that never overshoots and\n * stays visually distinct from `basis`, which cuts corners far more deeply.\n * The trade is that the path passes *near* each bend rather than exactly\n * through it — documented in docs/diagrams.md.\n */\nfunction naturalPath(points: Point[]): string {\n if (points.length < 3) return linearPath(points)\n\n /** Largest corner radius, in px. Kept small so edges stay readable. */\n const MAX_RADIUS = 12\n\n const parts: string[] = [moveTo(points[0]!)]\n\n for (let i = 1; i < points.length - 1; i++) {\n const prev = points[i - 1]!\n const corner = points[i]!\n const next = points[i + 1]!\n\n const inLen = Math.hypot(corner.x - prev.x, corner.y - prev.y)\n const outLen = Math.hypot(next.x - corner.x, next.y - corner.y)\n\n // A zero-length leg means duplicated routed points; there is no corner\n // to round, so pass straight through.\n if (inLen === 0 || outLen === 0) {\n parts.push(f` L ${corner.x} ${corner.y}`)\n continue\n }\n\n // Never consume more than half of either adjacent leg, so neighbouring\n // corners cannot overlap and invert the path.\n const radius = Math.min(MAX_RADIUS, inLen / 2, outLen / 2)\n\n const startX = corner.x - ((corner.x - prev.x) / inLen) * radius\n const startY = corner.y - ((corner.y - prev.y) / inLen) * radius\n const endX = corner.x + ((next.x - corner.x) / outLen) * radius\n const endY = corner.y + ((next.y - corner.y) / outLen) * radius\n\n // Straight up to the fillet, then a quadratic through the corner.\n parts.push(f` L ${startX} ${startY}`)\n parts.push(f` Q ${corner.x} ${corner.y}, ${endX} ${endY}`)\n }\n\n const last = points[points.length - 1]!\n parts.push(f` L ${last.x} ${last.y}`)\n\n return parts.join('')\n}\n\n/**\n * Right-angle staircase between consecutive points.\n *\n * `where` picks when the step happens: at the midpoint (`step`), immediately\n * (`stepBefore`), or at the end of the run (`stepAfter`) — matching d3's\n * curveStep family.\n *\n * The final segment is deliberately left straight, running along the original\n * approach direction. An SVG marker with `orient=\"auto\"` takes its angle from\n * the last path segment, so a staircase that ends on a horizontal leg points\n * the arrowhead sideways into the target node instead of at it. Keeping the\n * last leg means the arrow still aims the way the routed edge does.\n */\nfunction stepPath(points: Point[], where: 'mid' | 'before' | 'after'): string {\n const parts: string[] = [moveTo(points[0]!)]\n const lastIndex = points.length - 1\n\n for (let i = 0; i < lastIndex; i++) {\n const a = points[i]!\n const b = points[i + 1]!\n\n if (i === lastIndex - 1) {\n parts.push(f` L ${b.x} ${b.y}`)\n break\n }\n\n if (where === 'before') {\n parts.push(f` L ${a.x} ${b.y} L ${b.x} ${b.y}`)\n } else if (where === 'after') {\n parts.push(f` L ${b.x} ${a.y} L ${b.x} ${b.y}`)\n } else {\n const midY = (a.y + b.y) / 2\n parts.push(f` L ${a.x} ${midY} L ${b.x} ${midY} L ${b.x} ${b.y}`)\n }\n }\n\n return parts.join('')\n}\n\n/**\n * Build the `d` attribute for an edge's routed points under `curve`.\n *\n * A path with fewer than two points cannot be drawn; the caller already skips\n * those, but this degrades to an empty string rather than emitting `M`\n * followed by nothing.\n */\nexport function pointsToPath(\n points: Point[],\n curve: CurveStyle = 'linear',\n): string {\n if (points.length === 0) return ''\n if (points.length === 1) return moveTo(points[0]!)\n\n switch (curve) {\n case 'basis':\n return basisPath(points)\n case 'natural':\n return naturalPath(points)\n case 'step':\n return stepPath(points, 'mid')\n case 'stepBefore':\n return stepPath(points, 'before')\n case 'stepAfter':\n return stepPath(points, 'after')\n case 'linear':\n default:\n return linearPath(points)\n }\n}\n","/**\n * Shared ELK instance singleton.\n *\n * Uses elk.bundled.js (pure synchronous JS, ~1.6 MB) for all environments.\n * The singleton is created lazily on first use and cached forever.\n *\n * ELK's FakeWorker wraps both postMessage and onmessage in setTimeout(0),\n * making the normal API fully async. To bypass this:\n * 1. During construction, we capture setTimeout(0) callbacks and flush them\n * synchronously — this registers the layout algorithms immediately.\n * 2. For layout calls, we call dispatcher.saveDispatch() directly (skipping\n * the FakeWorker's postMessage setTimeout) and intercept the result via\n * rawWorker.onmessage (which the dispatcher calls synchronously).\n */\n\nimport type { ELK, ElkNode } from 'elkjs'\nimport ELKBundled from 'elkjs/lib/elk.bundled.js'\nimport type { LayoutCache } from '@zombie-mermaid/core'\n\n/** The message envelope ELK's FakeWorker passes to `dispatcher.saveDispatch()`\n * to request a layout run. Mirrors the shape elk-worker.min.js expects on\n * `data` for a `cmd: 'layout'` request — not part of elkjs's public types. */\ninterface ElkWorkerRequest {\n id: number\n cmd: 'layout'\n graph: ElkNode\n}\n\n/** The message envelope ELK's dispatcher passes back to `onmessage` once a\n * layout run completes. Mirrors elk-worker.min.js's response shape for a\n * `cmd: 'layout'` request — not part of elkjs's public types. */\ninterface ElkWorkerResponse {\n id: number\n data?: ElkNode\n error?: unknown\n}\n\ninterface RawFakeWorker {\n postMessage(msg: unknown): void\n onmessage: ((e: { data: ElkWorkerResponse }) => void) | null\n dispatcher: {\n saveDispatch(msg: { data: ElkWorkerRequest }): void\n }\n}\n\n/**\n * The shape of elkjs's bundled `ELK` instance that actually exists at\n * runtime, including the internal `worker` handle. elkjs's public `ELK`\n * type (from `elk-api.d.ts`) only declares `layout`/`knownLayout*`/\n * `terminateWorker` — it deliberately doesn't expose worker internals,\n * since those aren't a supported API. We rely on them anyway (see file\n * header), so this extends the public type with the internal piece we\n * touch instead of casting through `unknown`.\n */\ninterface ElkBundledInternal extends ELK {\n worker: { worker: RawFakeWorker }\n}\n\nlet elk: ELK | null = null\nlet rawWorker: RawFakeWorker | null = null\n\n// ============================================================================\n// Opt-in layout cache\n// ============================================================================\n\n/**\n * `LayoutCache`'s shape lives in `@zombie-mermaid/core`'s `types.ts`\n * because `RenderOptions.layoutCache` references it and `core` must not\n * type-import this package (zombie-mermaid#625). Re-exported here so this\n * module stays the single import site for everything layout-cache-related,\n * exactly as before the split.\n */\nexport type { LayoutCache }\n\n/**\n * Concrete shape of a `LayoutCache`, restated locally.\n *\n * `core`'s `LayoutCache` interface marks `map`/`maxSize` `@internal`\n * (see the comment on that interface in packages/core/src/types.ts), so\n * api-extractor strips them from `@zombie-mermaid/core`'s *published*\n * `dist/index.d.ts` — deliberately, so they stay invisible to\n * `@zombie-mermaid/core` consumers and to `zombie-mermaid`'s own public\n * types (which re-export `LayoutCache` from this package). As of #769,\n * `@zombie-mermaid/core` is a real, independently-built dependency of this\n * package rather than bundled source, so this module now resolves\n * `LayoutCache` through that same trimmed public declaration too — same as\n * any other consumer. This module is the one place, in or out of `core`,\n * that actually builds and mutates those fields (`createLayoutCache()`\n * below; the cache read/evict logic in `elkLayoutSync()`), so it restates\n * the concrete shape here — kept in sync by hand with `core`'s source of\n * truth — rather than either widening `core`'s public surface or fighting\n * api-extractor's trimming in the build config to get it back.\n */\ninterface LayoutCacheShape {\n map: Map<string, ElkNode>\n maxSize: number\n}\n\nconst DEFAULT_LAYOUT_CACHE_SIZE = 20\n\n/**\n * Create a new opt-in layout cache with a bounded size (default 20\n * entries). Once full, the least-recently-used entry is evicted to make\n * room for a new one.\n *\n * Pass the result to `elkLayoutSync()` directly, or via\n * `RenderOptions.layoutCache` (threaded through by `layoutGraphSync()`,\n * `layoutClassDiagramSync()`, and `layoutErDiagramSync()`) to memoize\n * layout across repeated renders of the same diagram + options.\n */\nexport function createLayoutCache(\n maxSize: number = DEFAULT_LAYOUT_CACHE_SIZE,\n): LayoutCache {\n if (!Number.isInteger(maxSize) || maxSize < 1) {\n throw new Error(\n `createLayoutCache: maxSize must be a positive integer, got ${maxSize}`,\n )\n }\n // Typed as the concrete shape first (see `LayoutCacheShape` above), then\n // returned as the public, trimmed `LayoutCache` — a plain widening\n // assignment, not a cast.\n const cache: LayoutCacheShape = { map: new Map(), maxSize }\n return cache\n}\n\n/**\n * Deterministically serialize a value for use as a cache key, sorting\n * object keys recursively so two structurally-equal ELK input graphs\n * always produce an identical string regardless of property-insertion\n * order. Plain `JSON.stringify()` does not guarantee that — and this\n * function is the only thing standing between a cache hit and returning\n * some *other* diagram's layout, so it can't be allowed to drift with\n * insertion order.\n *\n * The ELK input graph built by `mermaidToElk()` is plain JSON (arrays,\n * plain objects, strings, numbers, booleans — the shape ELK's own JSON\n * schema requires) with every render option that affects layout already\n * baked in (direction, spacing, per-subgraph overrides, etc.), so\n * serializing the graph itself is a complete and correct cache key: equal\n * serialized input always means equal ELK output, since ELK layout is a\n * pure function of its input graph.\n */\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(',')}]`\n }\n if (value !== null && typeof value === 'object') {\n const record = value as Record<string, unknown>\n const keys = Object.keys(record).sort()\n const entries = keys.map(\n (key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`,\n )\n return `{${entries.join(',')}}`\n }\n return JSON.stringify(value)\n}\n\n/**\n * Ensure the ELK singleton exists.\n *\n * Patches setTimeout during construction to capture and synchronously flush\n * the algorithm registration callback that ELK queues via setTimeout(0).\n * Without this, layout calls fail with \"algorithm not found\" until the\n * next macrotask.\n */\nfunction ensureElk(): RawFakeWorker {\n if (elk && rawWorker) return rawWorker\n\n // Capture setTimeout(0) callbacks queued during ELK construction\n const pending: (() => void)[] = []\n const origSetTimeout = globalThis.setTimeout\n // @ts-expect-error — simplified signature for our interception, not\n // assignment-compatible with the full `typeof setTimeout` overload set\n globalThis.setTimeout = (fn: () => void, delay?: number) => {\n if (delay === 0) {\n pending.push(fn)\n return 0\n }\n return origSetTimeout(fn, delay)\n }\n\n // Bun defines `self` (= globalThis) but not `document`, which tricks\n // elk-worker.min.js into taking the Web Worker branch instead of the\n // CJS branch. Temporarily hide `self` so it exports {Worker: FakeWorker}.\n // `lib` in tsconfig.json is `[\"ESNext\"]` (no `dom`), so `self`/`document`\n // aren't ambiently declared on `globalThis` — narrow to just the two\n // properties this probe touches instead of a blanket `Record`.\n const g = globalThis as { self?: unknown; document?: unknown }\n const hadSelf = 'self' in g\n const origSelf = g.self\n if (hadSelf && typeof g.document === 'undefined') {\n delete g.self\n }\n\n elk = new ELKBundled()\n if (!elk) {\n // Unreachable — `new ELKBundled()` always returns an instance — but\n // makes the invariant explicit rather than letting the cast below\n // silently paper over a null `elk` if that ever stopped being true.\n throw new Error('ELKBundled construction unexpectedly produced no instance')\n }\n\n // Restore self\n if (hadSelf) g.self = origSelf\n\n // Restore setTimeout immediately\n globalThis.setTimeout = origSetTimeout\n\n // Flush captured callbacks synchronously — registers layout algorithms\n pending.forEach((fn) => fn())\n\n // Cache the raw FakeWorker for elkLayoutSync()\n rawWorker = (elk as ElkBundledInternal).worker.worker\n return rawWorker\n}\n\n/**\n * Run ELK layout synchronously.\n *\n * Bypasses BOTH of ELK's setTimeout(0) wrappers:\n * - FakeWorker.postMessage wraps dispatch in setTimeout(0) — bypassed by\n * calling dispatcher.saveDispatch() directly\n * - PromisedWorker.onmessage wraps receive in setTimeout(0) — bypassed by\n * replacing rawWorker.onmessage with a direct interceptor\n *\n * @param cache - Optional opt-in layout cache (see `createLayoutCache()`).\n * When provided, a cache hit returns the previous result without running\n * ELK layout again. Omitted/undefined preserves the original\n * always-recompute behavior exactly.\n */\nexport function elkLayoutSync(graph: ElkNode, cache?: LayoutCache): ElkNode {\n // `cache`, when provided, is always an object this module itself built\n // via `createLayoutCache()` above — never anything constructed outside\n // this package. Restated as the concrete shape here (see\n // `LayoutCacheShape`'s doc comment) since the public `LayoutCache` type\n // this parameter is declared with intentionally hides `map`/`maxSize`.\n const internalCache = cache as LayoutCacheShape | undefined\n const cacheKey = internalCache ? stableStringify(graph) : undefined\n if (internalCache && cacheKey !== undefined) {\n const cached = internalCache.map.get(cacheKey)\n if (cached) {\n // Mark as most-recently-used: Map iteration order follows insertion\n // order, so a delete+re-set moves this entry to the end — which is\n // what the LRU eviction below relies on to find the *least*\n // recently used entry (the current first key).\n internalCache.map.delete(cacheKey)\n internalCache.map.set(cacheKey, cached)\n return cached\n }\n }\n\n const worker = ensureElk()\n\n let result: ElkNode | undefined\n let error: unknown\n\n // Replace onmessage to intercept the result synchronously\n // (the dispatcher calls this directly, without setTimeout)\n const origOnmessage = worker.onmessage\n worker.onmessage = (answer: { data: ElkWorkerResponse }) => {\n if (answer.data.error) {\n error = answer.data.error\n } else {\n result = answer.data.data\n }\n }\n\n // Call dispatcher.saveDispatch directly — bypasses FakeWorker.postMessage's\n // setTimeout(0) wrapper. The dispatcher processes the layout synchronously\n // and calls rawWorker.onmessage with the result.\n worker.dispatcher.saveDispatch({\n data: { id: 0, cmd: 'layout', graph },\n })\n\n // Restore original handler\n worker.onmessage = origOnmessage\n\n if (error) throw error\n if (!result) throw new Error('ELK layout did not return synchronously')\n\n if (internalCache && cacheKey !== undefined) {\n internalCache.map.set(cacheKey, result)\n if (internalCache.map.size > internalCache.maxSize) {\n // Map iteration order is insertion order, so the first key is the\n // least recently used (see the recency bump on hit, above).\n const oldestKey = internalCache.map.keys().next().value\n if (oldestKey !== undefined) internalCache.map.delete(oldestKey)\n }\n }\n\n return result\n}\n","// ============================================================================\n// Font metrics — character width estimates for Inter at different sizes.\n// Used to approximate text bounding boxes without DOM measurement.\n// These are calibrated for Inter's typical glyph widths.\n//\n// NOTE: Theme/color system has moved to packages/core/src/theme.ts. This file only\n// contains font metrics, spacing constants, and stroke widths.\n// ============================================================================\n\nimport { measureTextWidth } from '@zombie-mermaid/core'\n\n/** Average character width in px at the given font size and weight (proportional font) */\nexport function estimateTextWidth(\n text: string,\n fontSize: number,\n fontWeight: number,\n): number {\n // Delegate to variable-width character measurement for better accuracy\n // with mixed character sets (Latin narrow/wide, CJK, emoji, etc.)\n return measureTextWidth(text, fontSize, fontWeight)\n}\n\n/** Average character width in px for monospace fonts (uniform glyph width) */\nexport function estimateMonoTextWidth(text: string, fontSize: number): number {\n // Monospace fonts have uniform character width — 0.6 of fontSize matches actual\n // glyph widths for JetBrains Mono / SF Mono / Fira Code at small sizes (11px).\n // Previous value of 0.55 underestimated widths, causing class member labels to\n // extend beyond their box boundaries.\n return text.length * fontSize * 0.6\n}\n\n/** Monospace font family used for code-like text (class members, types) */\nexport const MONO_FONT = \"'JetBrains Mono'\" as const\n\n/** Full CSS fallback chain for monospace text */\nexport const MONO_FONT_STACK =\n `${MONO_FONT}, 'SF Mono', 'Fira Code', ui-monospace, monospace` as const\n\n/** Default font sizes used in the renderer (in px). Overridable via `RenderOptions.fontSizes`. */\nexport const FONT_SIZES = {\n /** Node label text */\n nodeLabel: 13,\n /** Edge label text */\n edgeLabel: 11,\n /** Subgraph header text */\n groupHeader: 12,\n} as const\n\n/** Resolved font-size set — same shape as {@link FONT_SIZES} but mutable numbers. */\nexport type FontSizes = { [K in keyof typeof FONT_SIZES]: number }\n\n/** Partial font-size overrides, as accepted by `RenderOptions.fontSizes`. */\nexport type FontSizeOptions = Partial<FontSizes>\n\n/**\n * Merge user-provided font-size overrides over the {@link FONT_SIZES} defaults.\n * Any field left unspecified (or `undefined`) falls back to its default.\n */\nexport function resolveFontSizes(overrides?: FontSizeOptions): FontSizes {\n return {\n nodeLabel: overrides?.nodeLabel ?? FONT_SIZES.nodeLabel,\n edgeLabel: overrides?.edgeLabel ?? FONT_SIZES.edgeLabel,\n groupHeader: overrides?.groupHeader ?? FONT_SIZES.groupHeader,\n }\n}\n\n/** Font weights used per element type */\nexport const FONT_WEIGHTS = {\n nodeLabel: 500,\n edgeLabel: 400,\n groupHeader: 600,\n} as const\n\n// ============================================================================\n// Spacing & sizing constants\n// ============================================================================\n\n/** Vertical gap between a subgraph header band and the content area below it (px).\n * Without this, nested subgraph headers sit flush against their parent's header band.\n * Increased from 8 to 12 to provide more clearance for edges routing near headers. */\nexport const GROUP_HEADER_CONTENT_PAD = 12\n\n/** Padding inside node shapes */\nexport const NODE_PADDING = {\n /** Horizontal padding inside rectangles/rounded/stadium (increased from 16 for better label fit) */\n horizontal: 20,\n /** Vertical padding inside rectangles/rounded/stadium */\n vertical: 10,\n /** Extra padding for diamond shapes (they need more space due to rotation) */\n diamondExtra: 24,\n} as const\n\n/** Stroke widths per element type (in px) */\nexport const STROKE_WIDTHS = {\n outerBox: 1,\n innerBox: 0.75,\n /** Edge connector stroke (increased from 0.75 for better visibility) */\n connector: 1,\n} as const\n\n/**\n * Vertical shift applied to all text elements for font-agnostic centering.\n *\n * Instead of relying on `dominant-baseline=\"central\"` (which each font interprets\n * differently based on its own ascent/descent metrics), we use the default alphabetic\n * baseline and shift down by 0.35em. This places the optical center of text at the\n * y coordinate, regardless of font family (Inter, JetBrains Mono, system fallbacks).\n *\n * The 0.35em value approximates the distance from alphabetic baseline to visual\n * center of Latin text. Using `em` units ensures it scales with font size.\n */\nexport const TEXT_BASELINE_SHIFT = '0.35em' as const\n\n/** Arrow head dimensions — matches spec: 8px wide × ~5px tall */\nexport const ARROW_HEAD = {\n width: 8,\n height: 5,\n} as const\n","/**\n * Shared defaults for the ELK-based layout engine.\n * Split out of layout-engine.ts so both the ELK conversion and the\n * post-processing stages (layer alignment, elk-result conversion) can\n * reference the same values without importing the top-level module.\n */\n\nimport { FONT_SIZES } from '../styles.ts'\n\n/** Default render options (layout-only) */\nexport const DEFAULTS = {\n font: 'Inter',\n padding: 40,\n nodeSpacing: 28,\n layerSpacing: 48,\n mergeEdges: true,\n thoroughness: 3,\n fontSizes: FONT_SIZES,\n} as const\n","/**\n * Shared ELK graph-*construction* helpers.\n *\n * The reverse direction (reading geometry back out of an ELK result) lives\n * in `./elk-adapter-utils.ts`; this module is its input-side counterpart.\n *\n * Three renderers build ELK's typed JSON input independently —\n * `./to-elk.ts` (flowchart + state), `../class/layout.ts`, and\n * `../er/layout.ts`. They differ in domain model and in which extra\n * `elk.*` options they set, but the primitives underneath are identical:\n * one direction mapping, one `elk.padding` string format, one leaf-node\n * shape, and one measured edge-label box. Those primitives live here so\n * the three call sites can't drift (issue #616).\n *\n * This module deliberately does **not** try to abstract over the three\n * diagram types' own graph shapes (subgraph nesting, hierarchical ports,\n * note links) — that would be an engine-neutral intermediate graph, which\n * is the separately-scoped work in #538.\n */\n\nimport type { ElkExtendedEdge, ElkLabel, ElkNode, LayoutOptions } from 'elkjs'\nimport type { Direction } from '@zombie-mermaid/core'\nimport { measureMultilineText } from '@zombie-mermaid/core'\nimport { FONT_WEIGHTS } from '../styles.ts'\n\n// ============================================================================\n// Direction\n// ============================================================================\n\n/** The four values ELK's `elk.direction` option accepts in this codebase. */\nexport type ElkDirection = 'DOWN' | 'UP' | 'LEFT' | 'RIGHT'\n\n/**\n * The `elk.direction` each diagram type falls back to when its source\n * carries no `direction` statement.\n *\n * These genuinely differ per diagram type, and the difference is\n * intentional rather than an accident of three separate implementations:\n *\n * - **flowchart / state** — `DOWN`. `MermaidGraph.direction` is a required\n * field the parser always fills in (defaulting to `TD`), so the fallback\n * is only a type-level backstop; the effective default is mermaid's own\n * top-down flowchart default.\n * - **class** — `DOWN`. `ClassDiagram` has no direction concept at all (see\n * `ClassRenderOptions` in `packages/core/src/types.ts`: class diagrams\n * \"have no `direction` or `curve` concept\"), so this is the renderer's\n * single fixed orientation, matching Mermaid's own TB class rendering —\n * which `../class/layout.ts` relies on to put a `note for X` above its\n * class.\n * - **C4** — `DOWN`. `C4Diagram.direction` is never set by the parser (C4\n * sources have no direction statement), only by `RenderOptions.direction`,\n * so an unset one is the top-down layout every C4 diagram is drawn in.\n * - **ER** — `RIGHT`. An ER diagram's `direction` is optional\n * (`ErDiagram.direction?`), left `undefined` when the source has no\n * `direction` statement, and this renderer has always laid those out\n * left-to-right. `direction TB`/`LR`/`BT`/`RL` in the source (or\n * `RenderOptions.direction`, applied via `withDirectionOverride`) still\n * wins over it.\n *\n * The *mappings* were never in conflict — both hand-rolled\n * `directionToElk()` implementations agreed on all five `Direction` values\n * (`LR`→`RIGHT`, `RL`→`LEFT`, `BT`→`UP`, `TD`/`TB`→`DOWN`). Only the\n * no-direction fallback differed, which is why it is a parameter here\n * rather than something to reconcile away.\n */\nexport const ELK_DIRECTION_FALLBACK = {\n flowchart: 'DOWN',\n state: 'DOWN',\n class: 'DOWN',\n er: 'RIGHT',\n c4: 'DOWN',\n} as const satisfies Record<string, ElkDirection>\n\n/**\n * Convert a Mermaid `direction` to ELK's `elk.direction` value.\n *\n * `fallback` covers the no-`direction`-statement case only — every member\n * of `Direction` has an explicit mapping. Pass the diagram type's entry\n * from `ELK_DIRECTION_FALLBACK` rather than a bare string literal, so the\n * per-type defaults stay documented in one place.\n */\nexport function directionToElk(\n dir: Direction | undefined,\n fallback: ElkDirection,\n): ElkDirection {\n switch (dir) {\n case 'LR':\n return 'RIGHT'\n case 'RL':\n return 'LEFT'\n case 'BT':\n return 'UP'\n case 'TD':\n case 'TB':\n return 'DOWN'\n default:\n return fallback\n }\n}\n\n// ============================================================================\n// Layout options\n// ============================================================================\n\n/** Per-side padding, for the asymmetric case (a subgraph's header gap). */\nexport interface ElkPaddingSides {\n top: number\n left: number\n bottom: number\n right: number\n}\n\n/**\n * Format ELK's `elk.padding` value. A single number applies to all four\n * sides; an object sets them individually.\n */\nexport function elkPadding(padding: number | ElkPaddingSides): string {\n const sides: ElkPaddingSides =\n typeof padding === 'number'\n ? { top: padding, left: padding, bottom: padding, right: padding }\n : padding\n return `[top=${sides.top},left=${sides.left},bottom=${sides.bottom},right=${sides.right}]`\n}\n\n/**\n * The `elk.*` options every one of the three graph builders sets, with the\n * values they all agree on. Callers spread the result and add their own\n * diagram-specific options on top.\n */\nexport function baseElkLayoutOptions(spec: {\n direction: ElkDirection\n nodeSpacing: number\n layerSpacing: number\n padding: number | ElkPaddingSides\n}): LayoutOptions {\n return {\n 'elk.algorithm': 'layered',\n 'elk.direction': spec.direction,\n 'elk.spacing.nodeNode': String(spec.nodeSpacing),\n 'elk.layered.spacing.nodeNodeBetweenLayers': String(spec.layerSpacing),\n 'elk.padding': elkPadding(spec.padding),\n 'elk.edgeRouting': 'ORTHOGONAL',\n }\n}\n\n// ============================================================================\n// Nodes\n// ============================================================================\n\n/**\n * Build a leaf ELK node.\n *\n * `label` is attached as an ELK label when supplied — the flowchart/state\n * path does that (ELK reads it for nothing, but `from-elk.ts` reads it\n * back); class and ER carry labels in their own side tables instead and\n * omit it. An empty-string label is still a label, so the check is\n * `undefined`, not truthiness.\n */\nexport function buildElkLeafNode(\n id: string,\n size: { width: number; height: number },\n label?: string,\n): ElkNode {\n const node: ElkNode = { id, width: size.width, height: size.height }\n if (label !== undefined) node.labels = [{ text: label }]\n return node\n}\n\n// ============================================================================\n// Edges\n// ============================================================================\n\n/**\n * Per-label layout options the flowchart/state path sets on every edge\n * label. Class and ER instead set `elk.edgeLabels.placement` once on the\n * root graph, so they pass no per-label options.\n */\nexport const INLINE_CENTERED_EDGE_LABEL: LayoutOptions = {\n 'elk.edgeLabels.inline': 'true',\n 'elk.edgeLabels.placement': 'CENTER',\n}\n\n/**\n * How a caller wants its edge labels measured and placed. `fontSize` is\n * the resolved `fontSizes.edgeLabel`; `layoutOptions`, when given, is\n * attached to the ELK label itself.\n */\nexport interface ElkEdgeLabelStyle {\n fontSize: number\n layoutOptions?: LayoutOptions\n}\n\n/**\n * Measure an edge label and build ELK's label box for it.\n *\n * The `+8` / `+6` are the horizontal/vertical breathing room all three\n * builders have always added around the measured text so ELK reserves a\n * slightly larger channel than the glyphs strictly need.\n */\nexport function buildElkEdgeLabel(\n text: string,\n style: ElkEdgeLabelStyle,\n): ElkLabel {\n const metrics = measureMultilineText(\n text,\n style.fontSize,\n FONT_WEIGHTS.edgeLabel,\n )\n const label: ElkLabel = {\n text,\n width: metrics.width + 8,\n height: metrics.height + 6,\n }\n // Copied, not aliased: every label previously got its own fresh options\n // literal, and ELK receives this graph by reference (see\n // `elkLayoutSync`'s `saveDispatch`) rather than through a structured\n // clone — so a shared object would be shared into ELK's own hands too.\n if (style.layoutOptions) label.layoutOptions = { ...style.layoutOptions }\n return label\n}\n\n/**\n * Build a single-source/single-target ELK edge, with a measured label when\n * `label` is non-empty. An absent or empty label leaves `labels` off the\n * edge entirely (rather than setting it to an empty array or `undefined`),\n * matching what all three builders did by hand.\n */\nexport function buildElkEdge(spec: {\n id: string\n source: string\n target: string\n label?: string\n labelStyle: ElkEdgeLabelStyle\n}): ElkExtendedEdge {\n const edge: ElkExtendedEdge = {\n id: spec.id,\n sources: [spec.source],\n targets: [spec.target],\n }\n if (spec.label) {\n edge.labels = [buildElkEdgeLabel(spec.label, spec.labelStyle)]\n }\n return edge\n}\n","/**\n * Graph conversion: MermaidGraph → ELK JSON.\n *\n * Split out of layout-engine.ts. This module only handles building ELK's\n * nested JSON input format from a parsed MermaidGraph — the reverse\n * direction (ELK result → PositionedGraph) lives in ./from-elk.ts.\n */\n\nimport type { ElkNode, ElkExtendedEdge, LayoutOptions } from 'elkjs'\nimport type {\n MermaidGraph,\n MermaidSubgraph,\n MermaidEdge,\n RenderOptions,\n} from '@zombie-mermaid/core'\nimport type { FontSizes } from '../styles.ts'\nimport { FONT_WEIGHTS, NODE_PADDING } from '../styles.ts'\nimport { measureMultilineText } from '@zombie-mermaid/core'\nimport { DEFAULTS } from './constants.ts'\nimport {\n ELK_DIRECTION_FALLBACK,\n INLINE_CENTERED_EDGE_LABEL,\n baseElkLayoutOptions,\n buildElkEdge,\n buildElkLeafNode,\n directionToElk as sharedDirectionToElk,\n type ElkDirection,\n type ElkEdgeLabelStyle,\n type ElkPaddingSides,\n} from './elk-graph-builder.ts'\n\n/**\n * Convert a Mermaid direction to an ELK direction, flowchart/state-style\n * (no direction at all → `DOWN`; see `ELK_DIRECTION_FALLBACK`).\n *\n * `MermaidGraph.direction` is required, so the fallback is unreachable\n * from this path in practice — it exists so every `Direction` maps through\n * the one shared table.\n */\nfunction directionToElk(dir: MermaidGraph['direction']): ElkDirection {\n return sharedDirectionToElk(dir, ELK_DIRECTION_FALLBACK.flowchart)\n}\n\n/**\n * The `elk.*` options the root graph and every subgraph container share.\n * Both add `elk.direction`, `elk.padding` and their own extras on top.\n */\nfunction containerLayoutOptions(spec: {\n direction: ElkDirection\n nodeSpacing: number\n layerSpacing: number\n padding: number | ElkPaddingSides\n}): LayoutOptions {\n return {\n ...baseElkLayoutOptions(spec),\n 'elk.spacing.edgeEdge': '12',\n 'elk.layered.spacing.edgeEdgeBetweenLayers': '12',\n 'elk.layered.spacing.edgeNodeBetweenLayers': '12',\n 'elk.layered.nodePlacement.bk.fixedAlignment': 'BALANCED',\n 'elk.contentAlignment': 'H_CENTER V_CENTER',\n }\n}\n\n/**\n * How flowchart/state edge labels are measured and placed: at the resolved\n * edge-label font size, inline and centered on the edge.\n */\nfunction edgeLabelStyle(opts: { fontSizes: FontSizes }): ElkEdgeLabelStyle {\n return {\n fontSize: opts.fontSizes.edgeLabel,\n layoutOptions: INLINE_CENTERED_EDGE_LABEL,\n }\n}\n\n// ============================================================================\n// Node sizing\n// ============================================================================\n\nfunction estimateNodeSize(\n id: string,\n label: string,\n shape: string,\n nodeLabelFontSize: number,\n): { width: number; height: number } {\n const metrics = measureMultilineText(\n label,\n nodeLabelFontSize,\n FONT_WEIGHTS.nodeLabel,\n )\n\n let width = metrics.width + NODE_PADDING.horizontal * 2\n let height = metrics.height + NODE_PADDING.vertical * 2\n\n if (shape === 'diamond') {\n const side = Math.max(width, height) + NODE_PADDING.diamondExtra\n width = side\n height = side\n }\n\n if (shape === 'circle' || shape === 'doublecircle') {\n const diameter = Math.ceil(Math.sqrt(width * width + height * height)) + 8\n width = shape === 'doublecircle' ? diameter + 12 : diameter\n height = width\n }\n\n if (shape === 'hexagon') {\n width += NODE_PADDING.horizontal\n }\n\n if (\n shape === 'trapezoid' ||\n shape === 'trapezoid-alt' ||\n shape === 'parallelogram' ||\n shape === 'parallelogram-alt'\n ) {\n // Sloped sides eat horizontal room at the label's baseline, so widen to\n // keep the label inside the polygon.\n width += NODE_PADDING.horizontal\n }\n\n if (shape === 'asymmetric') {\n width += 12\n }\n\n if (shape === 'cylinder') {\n height += 14\n }\n\n if (shape === 'state-start' || shape === 'state-end') {\n return { width: 28, height: 28 }\n }\n\n width = Math.max(width, 60)\n height = Math.max(height, 36)\n\n return { width, height }\n}\n\n// ============================================================================\n// Graph conversion: MermaidGraph → ELK JSON\n// ============================================================================\n\nexport interface ElkGraphNode extends ElkNode {\n children?: ElkGraphNode[]\n edges?: ElkExtendedEdge[]\n}\n\n/** Sentinel container key for hop/bridge edges that belong at the root level. */\nconst ROOT_CONTAINER = ' root'\n\n/**\n * Recursively check whether any subgraph (at any nesting depth) has a\n * direction override. The previous implementation only scanned top-level\n * subgraphs, silently missing overrides on nested subgraphs — which meant\n * `hierarchyHandling` stayed at `INCLUDE_CHILDREN` (where ELK ignores a\n * compound node's own `elk.direction`) for exactly the diagrams that need\n * `SEPARATE` the most.\n */\nfunction hasAnyDirectionOverride(subgraphs: MermaidSubgraph[]): boolean {\n for (const sg of subgraphs) {\n if (sg.direction !== undefined) return true\n if (hasAnyDirectionOverride(sg.children)) return true\n }\n return false\n}\n\n/** Recursively collect every node ID that is a member of `sg`, including nested descendants. */\nfunction collectAllMemberNodeIds(sg: MermaidSubgraph, out: Set<string>): void {\n for (const id of sg.nodeIds) out.add(id)\n for (const child of sg.children) collectAllMemberNodeIds(child, out)\n}\n\n/**\n * Real mermaid.js ignores a subgraph's own `direction` override once any of\n * its member nodes (including nested descendants) has an edge to something\n * outside the subgraph — the subgraph then inherits its parent's direction\n * instead. Per the docs: \"If any of a subgraph's nodes are linked to the\n * outside, subgraph direction will be ignored. Instead the subgraph will\n * inherit the direction of the parent graph.\"\n * https://mermaid.js.org/syntax/flowchart.html\n *\n * Verified against real mermaid.js output for nested subgraphs with\n * `direction LR` and an edge crossing the boundary (e.g. `B --> X` where X\n * is inside the subgraph): the crossing-linked nodes render at the same\n * x-coordinate with increasing y — stacked per the parent's direction,\n * despite the subgraph's own `direction LR`. Mirrors the equivalent ASCII\n * fix in src/ascii/converter.ts (subgraphDirectionIsHonored, issue #445).\n */\nfunction subgraphDirectionIsHonored(\n sg: MermaidSubgraph,\n graph: MermaidGraph,\n): boolean {\n const memberIds = new Set<string>()\n collectAllMemberNodeIds(sg, memberIds)\n if (memberIds.size === 0) return true\n\n const isInside = (id: string): boolean => memberIds.has(id) || id === sg.id\n\n return !graph.edges.some((e) => isInside(e.source) !== isInside(e.target))\n}\n\n/**\n * Build a map from subgraph ID to its full ancestor chain, outermost first,\n * ending with the subgraph itself (e.g. Inner nested in Outer → [\"Outer\", \"Inner\"]).\n */\nfunction buildSubgraphChains(\n subgraphs: MermaidSubgraph[],\n): Map<string, string[]> {\n const chains = new Map<string, string[]>()\n function walk(sg: MermaidSubgraph, ancestors: string[]): void {\n const chain = [...ancestors, sg.id]\n chains.set(sg.id, chain)\n for (const child of sg.children) walk(child, chain)\n }\n for (const sg of subgraphs) walk(sg, [])\n return chains\n}\n\n/** Length of the shared prefix of two ancestor chains. */\nfunction commonPrefixLength(a: string[], b: string[]): number {\n let i = 0\n while (i < a.length && i < b.length && a[i] === b[i]) i++\n return i\n}\n\n/**\n * Convert a MermaidGraph to ELK's nested JSON input format.\n *\n * Uses SEPARATE hierarchy handling for proper subgraph direction override support.\n * Cross-hierarchy edges use hierarchical ports to connect external and internal sections.\n */\nexport function mermaidToElk(\n graph: MermaidGraph,\n opts: Required<\n Pick<RenderOptions, 'font' | 'padding' | 'nodeSpacing' | 'layerSpacing'>\n > & { fontSizes: FontSizes },\n): ElkGraphNode {\n // Collect all node IDs that belong to subgraphs\n const subgraphNodeIds = new Set<string>()\n const subgraphIds = new Set<string>()\n for (const sg of graph.subgraphs) {\n subgraphIds.add(sg.id)\n collectSubgraphNodeIds(sg, subgraphNodeIds, subgraphIds)\n }\n\n // Build node-to-subgraph mapping for edge distribution\n const nodeToSubgraph = buildNodeToSubgraphMap(graph.subgraphs)\n\n // Full ancestor chain (outermost → innermost) for every subgraph, used to\n // decompose edges that cross more than one subgraph boundary.\n const subgraphChains = buildSubgraphChains(graph.subgraphs)\n const chainOf = (sgId: string | undefined): string[] =>\n sgId ? (subgraphChains.get(sgId) ?? []) : []\n\n // Classify edges into three categories:\n // 1. Internal edges (both endpoints in the same innermost subgraph)\n // 2. Root-level edges (neither endpoint in a subgraph)\n // 3. Cross-hierarchy edges (endpoints in different levels, possibly\n // several subgraph boundaries apart)\n const edgesBySubgraph = new Map<\n string | null,\n Array<{ index: number; edge: (typeof graph.edges)[0] }>\n >()\n edgesBySubgraph.set(null, []) // Root-level edges\n\n // Cross-hierarchy edges carry the full ancestor chain for each endpoint so\n // we can decompose them into a chain of sub-edges (one per crossed\n // boundary) rather than assuming a single hop.\n const crossHierarchyEdges: Array<{\n index: number\n edge: (typeof graph.edges)[0]\n srcChain: string[]\n tgtChain: string[]\n }> = []\n\n for (let i = 0; i < graph.edges.length; i++) {\n const edge = graph.edges[i]!\n const sourceSubgraph = nodeToSubgraph.get(edge.source)\n const targetSubgraph = nodeToSubgraph.get(edge.target)\n\n if (sourceSubgraph && sourceSubgraph === targetSubgraph) {\n // Internal edge: both endpoints in same innermost subgraph\n const internal = edgesBySubgraph.get(sourceSubgraph) ?? []\n edgesBySubgraph.set(sourceSubgraph, internal)\n internal.push({ index: i, edge })\n } else if (!sourceSubgraph && !targetSubgraph) {\n // Root-level edge: neither endpoint in a subgraph\n edgesBySubgraph.get(null)!.push({ index: i, edge })\n } else {\n // Cross-hierarchy edge: may cross one or more subgraph boundaries\n crossHierarchyEdges.push({\n index: i,\n edge,\n srcChain: chainOf(sourceSubgraph),\n tgtChain: chainOf(targetSubgraph),\n })\n }\n }\n\n // Determine if we need SEPARATE hierarchy handling.\n // We use SEPARATE when any subgraph — at any nesting depth — has a\n // direction override, since ELK's INCLUDE_CHILDREN mode ignores a nested\n // compound node's own `elk.direction`.\n const hasDirectionOverride = hasAnyDirectionOverride(graph.subgraphs)\n\n // Root ELK graph's children/edges — built up below across several loops,\n // then assembled into the elkGraph object literal at the very end so its\n // fields never need to be re-read through the (optional-in-the-library)\n // ElkNode.children/edges types.\n const rootChildren: ElkGraphNode[] = []\n const rootEdges: ElkExtendedEdge[] = []\n\n // Build the root ELK graph's layout options up front — the graph object\n // itself is assembled at the end, once rootChildren/rootEdges are full.\n const rootLayoutOptions: LayoutOptions = {\n ...containerLayoutOptions({\n direction: directionToElk(graph.direction),\n nodeSpacing: opts.nodeSpacing,\n layerSpacing: opts.layerSpacing,\n padding: opts.padding,\n }),\n 'elk.layered.thoroughness': String(DEFAULTS.thoroughness),\n 'elk.layered.highDegreeNodes.treatment': 'true',\n 'elk.layered.highDegreeNodes.threshold': '8',\n 'elk.layered.compaction.postCompaction.strategy':\n 'LEFT_RIGHT_CONSTRAINT_LOCKING',\n 'elk.layered.considerModelOrder.strategy': 'NODES_AND_EDGES',\n 'elk.layered.wrapping.strategy': 'OFF',\n // Use SEPARATE when subgraphs have direction overrides (enables proper direction handling)\n // Use INCLUDE_CHILDREN otherwise (simpler cross-hierarchy edge routing)\n 'elk.hierarchyHandling': hasDirectionOverride\n ? 'SEPARATE'\n : 'INCLUDE_CHILDREN',\n }\n\n // Ports to declare on each subgraph's ELK node, keyed by subgraph ID.\n const portsBySubgraph = new Map<string, Set<string>>()\n // Hop/bridge edges to inject into each container's own `edges` array.\n // Keyed by subgraph ID, or ROOT_CONTAINER for the top-level graph.\n const hopEdgesByContainer = new Map<string, ElkExtendedEdge[]>()\n\n function addPort(sgId: string, portId: string): void {\n const ports = portsBySubgraph.get(sgId) ?? new Set<string>()\n portsBySubgraph.set(sgId, ports)\n ports.add(portId)\n }\n function addHopEdge(containerId: string, elkEdge: ElkExtendedEdge): void {\n const edges = hopEdgesByContainer.get(containerId) ?? []\n hopEdgesByContainer.set(containerId, edges)\n edges.push(elkEdge)\n }\n\n // Decompose each cross-hierarchy edge into a chain of sub-edges, one per\n // subgraph boundary crossed, joined at explicit ELK ports. This is\n // required under `hierarchyHandling: SEPARATE`: ELK only resolves an edge\n // automatically when both endpoints are visible from the edge's container\n // (a direct child, or a port owned by the container or a direct child of\n // it) — it does not search further down the hierarchy. A single-hop port\n // (as used previously) is therefore only correct when one endpoint is a\n // direct child of the other endpoint's subgraph; for deeper nesting ELK\n // silently fails to route the edge at all (no sections in the result).\n //\n // For each side that needs to cross N boundaries to reach the lowest\n // common ancestor (LCA) of the source and target subgraphs, we add one\n // port per boundary and chain sub-edges between them, walking outward\n // from the endpoint to the LCA. The final \"bridge\" sub-edge (still ID\n // `e{index}`, carrying the edge label) lives directly in the LCA's own\n // `edges` array (or at root, if the LCA is the root graph).\n if (hasDirectionOverride) {\n for (const { index, edge, srcChain, tgtChain } of crossHierarchyEdges) {\n const commonLen = commonPrefixLength(srcChain, tgtChain)\n\n let sourceRef = edge.source\n if (srcChain.length > commonLen) {\n for (let i = srcChain.length - 1; i >= commonLen; i--) {\n const sgId = srcChain[i]!\n const portId = `${sgId}_out_${index}`\n addPort(sgId, portId)\n const fromRef =\n i === srcChain.length - 1\n ? edge.source\n : `${srcChain[i + 1]}_out_${index}`\n addHopEdge(sgId, {\n id: `e${index}_s${i}`,\n sources: [fromRef],\n targets: [portId],\n })\n }\n sourceRef = `${srcChain[commonLen]}_out_${index}`\n }\n\n let targetRef = edge.target\n if (tgtChain.length > commonLen) {\n for (let i = commonLen; i <= tgtChain.length - 1; i++) {\n const sgId = tgtChain[i]!\n const portId = `${sgId}_in_${index}`\n addPort(sgId, portId)\n const toRef =\n i === tgtChain.length - 1\n ? edge.target\n : `${tgtChain[i + 1]}_in_${index}`\n addHopEdge(sgId, {\n id: `e${index}_t${i}`,\n sources: [portId],\n targets: [toRef],\n })\n }\n targetRef = `${tgtChain[commonLen]}_in_${index}`\n }\n\n const bridgeContainer =\n commonLen > 0 ? srcChain[commonLen - 1]! : ROOT_CONTAINER\n addHopEdge(\n bridgeContainer,\n buildElkEdge({\n id: `e${index}`,\n source: sourceRef,\n target: targetRef,\n label: edge.label,\n labelStyle: edgeLabelStyle(opts),\n }),\n )\n }\n }\n\n // Add top-level nodes (those not in any subgraph).\n for (const [id, node] of graph.nodes) {\n if (!subgraphNodeIds.has(id) && !subgraphIds.has(id)) {\n const size = estimateNodeSize(\n id,\n node.label,\n node.shape,\n opts.fontSizes.nodeLabel,\n )\n rootChildren.push(buildElkLeafNode(id, size, node.label))\n }\n }\n\n // Add subgraphs as compound nodes with children and their internal\n // edges. Sibling subgraphs are appended in *reverse* declaration order\n // (only relative to each other — their position as a group, before or\n // after the top-level leaf nodes above, is left as-is) to match real\n // mermaid.js's own sibling-subgraph order, which is what ELK's\n // `considerModelOrder` uses as a tie-break during crossing minimization\n // and thus what ultimately decides left-right sibling order.\n //\n // Verified against mermaid@11.17.2's bundled flowDb.getData()\n // (node_modules/mermaid/dist/mermaid.min.js): it builds the node list fed\n // to the layout engine by iterating the parsed `subGraphs` array\n // *backwards* to emit cluster/subgraph nodes. Since a compound node's\n // `children()` order in the resulting graph is exactly the order its\n // child nodes were inserted, sibling subgraphs under the same parent end\n // up in reversed declaration order — see issue #444.\n //\n // Deliberately scoped to *only* the sibling-subgraph reversal, not a\n // wholesale \"all clusters before all leaves\" reordering (which mermaid's\n // own mechanism also does, globally): moving the whole subgraphs group\n // ahead of top-level leaf nodes changed which edge in a cycle ELK treats\n // as a feedback edge for a sample mixing top-level leaves and a\n // subgraph in a cyclic flow (e.g. \"CI/CD Pipeline\"), reordering the\n // entire rank structure rather than just left-right sibling position —\n // a much bigger, unreviewed blast radius than the reported bug needs.\n for (const sg of [...graph.subgraphs].reverse()) {\n rootChildren.push(\n subgraphToElk(\n sg,\n graph,\n opts,\n edgesBySubgraph,\n portsBySubgraph,\n hopEdgesByContainer,\n graph.direction,\n ),\n )\n }\n\n // Add root-level edges. Self-loops (edge.source === edge.target) are\n // excluded — ELK has no native self-loop layout and produces a degenerate\n // zero-length-span polyline for them; from-elk.ts synthesizes a proper\n // side loop for these once node positions are known instead.\n for (const { index, edge } of edgesBySubgraph.get(null)!) {\n if (edge.source === edge.target) continue\n rootEdges.push(\n buildElkEdge({\n id: `e${index}`,\n source: edge.source,\n target: edge.target,\n label: edge.label,\n labelStyle: edgeLabelStyle(opts),\n }),\n )\n }\n\n if (hasDirectionOverride) {\n // Cross-hierarchy edges were already decomposed into hop/bridge edges\n // above; the ones whose LCA is the root graph belong here.\n for (const elkEdge of hopEdgesByContainer.get(ROOT_CONTAINER) ?? []) {\n rootEdges.push(elkEdge)\n }\n } else {\n // No direction overrides anywhere: hierarchyHandling is INCLUDE_CHILDREN,\n // which lets ELK route cross-hierarchy edges automatically as long as\n // they're declared with their raw node IDs (no port decomposition\n // needed).\n for (const { index, edge } of crossHierarchyEdges) {\n rootEdges.push(\n buildElkEdge({\n id: `e${index}`,\n source: edge.source,\n target: edge.target,\n label: edge.label,\n labelStyle: edgeLabelStyle(opts),\n }),\n )\n }\n }\n\n return {\n id: 'root',\n layoutOptions: rootLayoutOptions,\n children: rootChildren,\n edges: rootEdges,\n }\n}\n\n/**\n * Convert a MermaidSubgraph to an ELK compound node.\n * Includes internal edges (edges where both endpoints are in this subgraph)\n * so that the subgraph's direction override is respected by ELK.\n *\n * When using SEPARATE hierarchy handling (for direction override support),\n * also adds hierarchical ports for cross-hierarchy edges.\n *\n * `inheritedDirection` is the effective direction of the nearest ancestor\n * (a subgraph's own honored override, or ultimately the root graph's\n * direction) — used when this subgraph's own override is dropped per\n * `subgraphDirectionIsHonored`. Explicitly propagating it, rather than\n * leaving `elk.direction` unset and relying on ELK's own property\n * inheritance under `hierarchyHandling: SEPARATE`, keeps the \"inherit the\n * parent's direction\" behavior correct and independent of ELK's inheritance\n * semantics for that property.\n */\nfunction subgraphToElk(\n sg: MermaidSubgraph,\n graph: MermaidGraph,\n opts: Required<\n Pick<RenderOptions, 'font' | 'padding' | 'nodeSpacing' | 'layerSpacing'>\n > & { fontSizes: FontSizes },\n edgesBySubgraph: Map<\n string | null,\n Array<{ index: number; edge: MermaidEdge }>\n >,\n portsBySubgraph: Map<string, Set<string>>,\n hopEdgesByContainer: Map<string, ElkExtendedEdge[]>,\n inheritedDirection: MermaidGraph['direction'],\n): ElkGraphNode {\n // Apply this subgraph's own direction override only if it's actually\n // honored (no member node has an edge crossing the boundary); otherwise\n // fall back to the inherited (parent) direction, per mermaid.js's\n // documented precedence rule.\n const effectiveDirection =\n sg.direction !== undefined && subgraphDirectionIsHonored(sg, graph)\n ? sg.direction\n : inheritedDirection\n\n const layoutOptions: LayoutOptions = containerLayoutOptions({\n direction: directionToElk(effectiveDirection),\n nodeSpacing: opts.nodeSpacing,\n layerSpacing: opts.layerSpacing,\n // Top = headerHeight(28) + gap(16) to match bottom padding\n padding: { top: 44, left: 16, bottom: 16, right: 16 },\n })\n\n // Ports, built before children/edges since they don't depend on them.\n let elkPorts: ElkNode['ports']\n const ports = portsBySubgraph.get(sg.id)\n if (ports && ports.size > 0) {\n elkPorts = [...ports].map((portId) => ({\n id: portId,\n // Port side is determined by ELK based on edge direction\n }))\n }\n\n // Add direct child (leaf) nodes, in forward declaration order.\n const children: ElkGraphNode[] = []\n for (const nodeId of sg.nodeIds) {\n const node = graph.nodes.get(nodeId)\n if (node) {\n const size = estimateNodeSize(\n nodeId,\n node.label,\n node.shape,\n opts.fontSizes.nodeLabel,\n )\n children.push(buildElkLeafNode(nodeId, size, node.label))\n }\n }\n\n // Add nested subgraphs recursively, in reverse declaration order relative\n // to *each other* only (their position as a group, before or after the\n // leaf nodes above, is left as-is — see the matching comment and\n // rationale in `mermaidToElk`) — matching real mermaid.js's own sibling-\n // subgraph order (issue #444).\n for (const child of [...sg.children].reverse()) {\n children.push(\n subgraphToElk(\n child,\n graph,\n opts,\n edgesBySubgraph,\n portsBySubgraph,\n hopEdgesByContainer,\n effectiveDirection,\n ),\n )\n }\n\n // Add internal edges (edges where both endpoints are in this subgraph).\n // Self-loops are excluded — see the matching comment in mermaidToElk.\n const edges: ElkExtendedEdge[] = []\n const internalEdges = edgesBySubgraph.get(sg.id) ?? []\n for (const { index, edge } of internalEdges) {\n if (edge.source === edge.target) continue\n edges.push(\n buildElkEdge({\n id: `e${index}`,\n source: edge.source,\n target: edge.target,\n label: edge.label,\n labelStyle: edgeLabelStyle(opts),\n }),\n )\n }\n\n // Add hop/bridge edges whose lowest common ancestor is this subgraph.\n // These connect boundary ports to internal nodes (single-hop case), chain\n // ports between two nested levels (multi-hop case), or bridge two ports\n // belonging to direct children of this subgraph (the actual crossing\n // point between the source-side and target-side chains).\n for (const elkEdge of hopEdgesByContainer.get(sg.id) ?? []) {\n edges.push(elkEdge)\n }\n\n return {\n id: sg.id,\n layoutOptions,\n labels: sg.label ? [{ text: sg.label }] : undefined,\n ports: elkPorts,\n children,\n edges,\n }\n}\n\n/** Recursively collect all node IDs that belong to any subgraph */\nfunction collectSubgraphNodeIds(\n sg: MermaidSubgraph,\n nodeIds: Set<string>,\n subgraphIds: Set<string>,\n): void {\n for (const id of sg.nodeIds) {\n nodeIds.add(id)\n }\n for (const child of sg.children) {\n subgraphIds.add(child.id)\n collectSubgraphNodeIds(child, nodeIds, subgraphIds)\n }\n}\n\n/**\n * Build a mapping from node ID to its containing subgraph ID.\n * For nested subgraphs, maps to the innermost containing subgraph.\n * Nodes not in any subgraph are not included in the map.\n */\nfunction buildNodeToSubgraphMap(\n subgraphs: MermaidSubgraph[],\n): Map<string, string> {\n const map = new Map<string, string>()\n\n function traverse(sg: MermaidSubgraph): void {\n // Map all direct child nodes to this subgraph\n for (const nodeId of sg.nodeIds) {\n map.set(nodeId, sg.id)\n }\n // Recursively process nested subgraphs (they override parent mapping)\n for (const child of sg.children) {\n traverse(child)\n }\n }\n\n for (const sg of subgraphs) {\n traverse(sg)\n }\n\n return map\n}\n","/**\n * Shape-aware edge clipping utilities.\n *\n * ELK.js treats all nodes as rectangles for edge routing. For non-rectangular\n * shapes like diamonds, this causes edges to terminate at the bounding box\n * boundary instead of the actual shape vertices.\n *\n * This module provides utilities to clip edge endpoints to actual shape\n * boundaries after ELK layout is complete.\n */\n\nimport type { Point, PositionedNode } from '@zombie-mermaid/core'\n\n/**\n * Clip an edge endpoint to the actual shape boundary of a node.\n *\n * @param points - The edge points array\n * @param node - The node to clip to\n * @param isStart - True if clipping the start point (source), false for end (target)\n * @returns New points array with clipped endpoint\n */\nexport function clipEdgeToShape(\n points: Point[],\n node: PositionedNode,\n isStart: boolean,\n): Point[] {\n if (points.length < 2) return points\n\n // Only clip non-rectangular shapes\n if (\n node.shape === 'rectangle' ||\n node.shape === 'rounded' ||\n node.shape === 'stadium'\n ) {\n return points\n }\n\n const result = [...points]\n\n if (node.shape === 'diamond') {\n if (isStart) {\n result[0] = clipToDiamond(points[0]!, points[1]!, node)\n } else {\n const lastIdx = points.length - 1\n result[lastIdx] = clipToDiamond(\n points[lastIdx]!,\n points[lastIdx - 1]!,\n node,\n )\n }\n }\n // Future: add clipping for hexagon, circle, etc.\n\n return result\n}\n\n/**\n * Clip a point to the diamond shape boundary using ray-polygon intersection.\n *\n * Diamond vertices are at the midpoints of the bounding box sides:\n * - Top: (cx, y)\n * - Right: (x + w, cy)\n * - Bottom: (cx, y + h)\n * - Left: (x, cy)\n *\n * For orthogonal edges, we extend the final segment as a ray and find where\n * it intersects the diamond boundary. This preserves orthogonality while\n * ensuring the edge terminates at the actual diamond shape.\n *\n * @param endpoint - The edge endpoint to clip\n * @param adjacent - The adjacent point (to determine ray direction)\n * @param node - The diamond node\n * @returns Clipped point on the diamond boundary\n */\nfunction clipToDiamond(\n endpoint: Point,\n adjacent: Point,\n node: PositionedNode,\n): Point {\n const cx = node.x + node.width / 2\n const cy = node.y + node.height / 2\n\n // Diamond vertices\n const top: Point = { x: cx, y: node.y }\n const right: Point = { x: node.x + node.width, y: cy }\n const bottom: Point = { x: cx, y: node.y + node.height }\n const left: Point = { x: node.x, y: cy }\n\n // Determine approach direction from adjacent point\n const dx = endpoint.x - adjacent.x\n const dy = endpoint.y - adjacent.y\n\n // For orthogonal edges, one of dx or dy will be ~0\n const isVertical = Math.abs(dx) < Math.abs(dy)\n\n if (isVertical) {\n // Vertical ray at x = endpoint.x\n const rayX = endpoint.x\n\n if (dy > 0) {\n // Coming from above (moving down) → intersect with top half of diamond\n // Top half edges: left-top (left → top) and top-right (top → right)\n if (rayX <= cx) {\n // Intersect with left-top edge (from left vertex to top vertex)\n return intersectVerticalRayWithEdge(rayX, left, top) ?? top\n } else {\n // Intersect with top-right edge (from top vertex to right vertex)\n return intersectVerticalRayWithEdge(rayX, top, right) ?? top\n }\n } else {\n // Coming from below (moving up) → intersect with bottom half of diamond\n // Bottom half edges: left-bottom (bottom → left) and bottom-right (right → bottom)\n if (rayX <= cx) {\n // Intersect with bottom-left edge (from bottom vertex to left vertex)\n return intersectVerticalRayWithEdge(rayX, bottom, left) ?? bottom\n } else {\n // Intersect with bottom-right edge (from right vertex to bottom vertex)\n return intersectVerticalRayWithEdge(rayX, right, bottom) ?? bottom\n }\n }\n } else {\n // Horizontal ray at y = endpoint.y\n const rayY = endpoint.y\n\n if (dx > 0) {\n // Coming from left (moving right) → intersect with left half of diamond\n // Left half edges: top-left (top → left) and left-bottom (left → bottom)\n if (rayY <= cy) {\n // Intersect with top-left edge (from top vertex to left vertex)\n return intersectHorizontalRayWithEdge(rayY, top, left) ?? left\n } else {\n // Intersect with left-bottom edge (from left vertex to bottom vertex)\n return intersectHorizontalRayWithEdge(rayY, left, bottom) ?? left\n }\n } else {\n // Coming from right (moving left) → intersect with right half of diamond\n // Right half edges: top-right (top → right) and right-bottom (right → bottom)\n if (rayY <= cy) {\n // Intersect with top-right edge (from top vertex to right vertex)\n return intersectHorizontalRayWithEdge(rayY, top, right) ?? right\n } else {\n // Intersect with right-bottom edge (from right vertex to bottom vertex)\n return intersectHorizontalRayWithEdge(rayY, right, bottom) ?? right\n }\n }\n }\n}\n\n/**\n * Find intersection of a horizontal ray (y = rayY) with a line segment.\n * Returns the intersection point or null if no intersection.\n */\nfunction intersectHorizontalRayWithEdge(\n rayY: number,\n p1: Point,\n p2: Point,\n): Point | null {\n const dy = p2.y - p1.y\n if (Math.abs(dy) < 0.001) {\n // Edge is horizontal, no single intersection\n return null\n }\n\n const t = (rayY - p1.y) / dy\n if (t < 0 || t > 1) {\n // Intersection outside the edge segment\n return null\n }\n\n const x = p1.x + t * (p2.x - p1.x)\n return { x, y: rayY }\n}\n\n/**\n * Find intersection of a vertical ray (x = rayX) with a line segment.\n * Returns the intersection point or null if no intersection.\n */\nfunction intersectVerticalRayWithEdge(\n rayX: number,\n p1: Point,\n p2: Point,\n): Point | null {\n const dx = p2.x - p1.x\n if (Math.abs(dx) < 0.001) {\n // Edge is vertical, no single intersection\n return null\n }\n\n const t = (rayX - p1.x) / dx\n if (t < 0 || t > 1) {\n // Intersection outside the edge segment\n return null\n }\n\n const y = p1.y + t * (p2.y - p1.y)\n return { x: rayX, y }\n}\n","/**\n * Layer alignment — snap same-layer nodes to a uniform position.\n * Split out of layout-engine.ts as a self-contained post-processing pass\n * over a PositionedGraph's nodes/edges.\n */\n\nimport type {\n PositionedNode,\n PositionedEdge,\n Direction,\n} from '@zombie-mermaid/core'\nimport { DEFAULTS } from './constants.ts'\n\n/**\n * ELK's orthogonal edge routing staggers nodes within the same layer to create\n * space for edge bends. This post-processing step groups nodes into layers and\n * snaps them to the same flow-axis coordinate (Y for TD/TB, X for LR/RL).\n *\n * Grouping uses proximity along the flow axis: within a layer, ELK's stagger\n * is always less than layerSpacing (bounded by edge routing channels), while\n * adjacent layers are separated by at least layerSpacing + nodeHeight.\n * A threshold of 0.75 * layerSpacing cleanly separates these cases.\n *\n * Directly connected nodes (sharing an edge) are never merged into the same\n * layer group as an additional safety check.\n *\n * Edge endpoints connected to shifted nodes are adjusted proportionally.\n * Intermediate bend points are left unchanged — edge bundling or clipping\n * will recalculate them afterwards.\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// a bounds-checked array access, each backed by an explicit check earlier\n// in the same scope — `sorted[0]!` and the `sorted[i]!`/`sorted[i - 1]!`\n// loop-index accesses below are guaranteed by the `nodes.length === 0`\n// early return plus the `for (let i = 1; i < sorted.length; ...)` loop\n// bound; `edge.points[0]!`, `edge.points[1]!`, and the\n// `edge.points[edge.points.length - 1]!` / `[edge.points.length - 2]!`\n// pairs are guaranteed by the `edge.points.length < 2` early `continue`\n// (and, redundantly, the `edge.points.length > 1` checks right above each\n// access). This is the same idiom already accepted as justified elsewhere\n// in this codebase (see src/parser.ts, PR #158, and this directory's\n// edge-bundling.ts/from-elk.ts audit, PR #150, which didn't reach this\n// file). `noUncheckedIndexedAccess` can't see any of these guarantees,\n// but removing the `!` would only replace a proven-safe assertion with an\n// unreachable guard. Left as-is; no behavior change.\nexport function alignLayerNodes(\n nodes: PositionedNode[],\n edges: PositionedEdge[],\n direction: Direction,\n): void {\n if (nodes.length === 0) return\n\n const isHorizontal = direction === 'LR' || direction === 'RL'\n\n // Build set of directly-connected node pairs.\n // Nodes connected by an edge must not be merged into the same layer.\n const connectedPairs = new Set<string>()\n for (const edge of edges) {\n connectedPairs.add(`${edge.source}:${edge.target}`)\n connectedPairs.add(`${edge.target}:${edge.source}`)\n }\n\n // ELK's stagger creates small gaps between adjacent nodes in the same layer\n // (typically edgeEdge spacing = 12px per routing channel). Adjacent layers\n // are separated by at least layerSpacing (48px). We use single-linkage\n // clustering: a node joins the current layer if the gap from the previous\n // node (in sorted order) is within threshold, AND it has no direct edge to\n // any node already in the layer.\n const THRESHOLD = DEFAULTS.layerSpacing * 0.6\n\n // Sort nodes by flow-axis position\n const sorted = [...nodes].sort((a, b) =>\n isHorizontal ? a.x - b.x : a.y - b.y,\n )\n\n const layers: PositionedNode[][] = []\n let currentLayer: PositionedNode[] = [sorted[0]!]\n\n for (let i = 1; i < sorted.length; i++) {\n const pos = isHorizontal ? sorted[i]!.x : sorted[i]!.y\n const prevPos = isHorizontal ? sorted[i - 1]!.x : sorted[i - 1]!.y\n // Single-linkage: compare with previous node, not layer start\n const gap = pos - prevPos\n // Check if this node is connected to any node already in the current layer\n const hasEdgeToLayer = currentLayer.some((n) =>\n connectedPairs.has(`${n.id}:${sorted[i]!.id}`),\n )\n if (gap <= THRESHOLD && !hasEdgeToLayer) {\n currentLayer.push(sorted[i]!)\n } else {\n layers.push(currentLayer)\n currentLayer = [sorted[i]!]\n }\n }\n layers.push(currentLayer)\n\n // Snap each layer's nodes to the layer's center position\n const deltas = new Map<string, number>() // nodeId → shift amount\n\n for (const layer of layers) {\n if (layer.length <= 1) continue\n\n const positions = layer.map((n) => (isHorizontal ? n.x : n.y))\n const min = Math.min(...positions)\n const max = Math.max(...positions)\n if (max - min <= 1) continue // Already aligned\n\n // Use the center of the range as the snap target\n const target = (min + max) / 2\n\n for (const node of layer) {\n const oldPos = isHorizontal ? node.x : node.y\n const delta = target - oldPos\n if (Math.abs(delta) > 0.5) {\n if (isHorizontal) {\n node.x = target\n } else {\n node.y = target\n }\n deltas.set(node.id, delta)\n }\n }\n }\n\n if (deltas.size === 0) return\n\n // Adjust edge endpoints to match shifted node positions\n for (const edge of edges) {\n if (edge.points.length < 2) continue\n\n const srcDelta = deltas.get(edge.source)\n const tgtDelta = deltas.get(edge.target)\n\n if (srcDelta != null) {\n // Shift first point and any subsequent points in the initial vertical/horizontal run\n const first = edge.points[0]!\n if (isHorizontal) {\n first.x += srcDelta\n // Shift second point if it's part of a straight vertical exit\n if (\n edge.points.length > 1 &&\n edge.points[1]!.x === first.x - srcDelta\n ) {\n edge.points[1]!.x += srcDelta\n }\n } else {\n first.y += srcDelta\n if (\n edge.points.length > 1 &&\n edge.points[1]!.y === first.y - srcDelta\n ) {\n edge.points[1]!.y += srcDelta\n }\n }\n }\n\n if (tgtDelta != null) {\n const last = edge.points[edge.points.length - 1]!\n if (isHorizontal) {\n last.x += tgtDelta\n if (edge.points.length > 1) {\n const prev = edge.points[edge.points.length - 2]!\n if (prev.x === last.x - tgtDelta) prev.x += tgtDelta\n }\n } else {\n last.y += tgtDelta\n if (edge.points.length > 1) {\n const prev = edge.points[edge.points.length - 2]!\n if (prev.y === last.y - tgtDelta) prev.y += tgtDelta\n }\n }\n }\n }\n}\n","/**\n * Edge bundling — merge fan-out / fan-in edge paths into shared trunks.\n * Split out of layout-engine.ts as a self-contained post-processing pass\n * over a PositionedGraph's nodes/edges/groups.\n */\n\nimport type {\n PositionedEdge,\n PositionedNode,\n PositionedGroup,\n Direction,\n Point,\n} from '@zombie-mermaid/core'\n\n/*\n * Shrink applied to a node box before testing it against a bundled path, in px.\n * A trunk that merely grazes a node's border reads as clean, so only a genuine\n * overlap should disqualify a bundle.\n */\nconst NODE_CLEARANCE = 0.5\n\n/**\n * True when every segment of `points` stays clear of all `nodes`, ignoring the\n * ones named in `skipIds` (an edge's own endpoints, which it must touch).\n *\n * Bundling replaces a routed path with a trunk plus a straight branch to each\n * target. That branch is only safe when it spans the gap between two adjacent\n * layers; a target further away makes it cross every layer in between — and\n * whatever nodes sit in them. The layout engine already routed those edges\n * around the obstacles, so a bundle that would collide is rejected and the\n * original routing kept.\n *\n * Paths here are rectilinear, so each segment is tested as a degenerate\n * rectangle against each node's box.\n */\nfunction pathClearOfNodes(\n points: Point[],\n nodes: PositionedNode[],\n skipIds: Set<string>,\n): boolean {\n for (let i = 0; i < points.length - 1; i++) {\n const a = points[i]!\n const b = points[i + 1]!\n const segMinX = Math.min(a.x, b.x)\n const segMaxX = Math.max(a.x, b.x)\n const segMinY = Math.min(a.y, b.y)\n const segMaxY = Math.max(a.y, b.y)\n\n for (const node of nodes) {\n if (skipIds.has(node.id)) continue\n const minX = node.x + NODE_CLEARANCE\n const maxX = node.x + node.width - NODE_CLEARANCE\n const minY = node.y + NODE_CLEARANCE\n const maxY = node.y + node.height - NODE_CLEARANCE\n // A node smaller than the clearance on either axis can't block anything.\n if (minX >= maxX || minY >= maxY) continue\n if (\n segMinX < maxX &&\n segMaxX > minX &&\n segMinY < maxY &&\n segMaxY > minY\n ) {\n return false\n }\n }\n }\n return true\n}\n\n/**\n * Find all groups (outermost first) that geometrically contain the given point.\n */\nfunction findGroupsContainingPoint(\n x: number,\n y: number,\n groups: PositionedGroup[],\n): PositionedGroup[] {\n const result: PositionedGroup[] = []\n for (const g of groups) {\n if (x >= g.x && x <= g.x + g.width && y >= g.y && y <= g.y + g.height) {\n result.push(g)\n result.push(...findGroupsContainingPoint(x, y, g.children))\n }\n }\n return result\n}\n\n/**\n * If `junction` falls inside a group that doesn't contain the reference node,\n * move it just outside the outermost such group boundary.\n */\nfunction adjustJunctionForGroups(\n junctionMain: number, // the junction coordinate along the flow axis (Y for TD, X for LR)\n refX: number, // reference node center X (for finding its groups)\n refY: number, // reference node center Y\n groups: PositionedGroup[],\n direction: Direction,\n): number {\n const GAP = 12\n const isLR = direction === 'LR'\n const isRL = direction === 'RL'\n const isBT = direction === 'BT'\n const isHorizontal = isLR || isRL\n\n // Groups containing the reference node\n const refGroupIds = new Set(\n findGroupsContainingPoint(refX, refY, groups).map((g) => g.id),\n )\n\n // Check where the junction point would be along the trunk\n const probeX = isHorizontal ? junctionMain : refX\n const probeY = isHorizontal ? refY : junctionMain\n const junctionGroups = findGroupsContainingPoint(probeX, probeY, groups)\n\n // Find outermost group containing the junction but NOT the reference node\n const crossingGroup = junctionGroups.find((g) => !refGroupIds.has(g.id))\n if (!crossingGroup) return junctionMain\n\n // Move junction just outside this group\n if (isLR) return crossingGroup.x - GAP\n if (isRL) return crossingGroup.x + crossingGroup.width + GAP\n if (isBT) return crossingGroup.y + crossingGroup.height + GAP\n return crossingGroup.y - GAP // TD\n}\n\n/**\n * Bundle fan-out and fan-in edge paths so they share a common trunk segment.\n *\n * For fan-out (one source → N targets), all edges exit the source at the same\n * point, travel along a shared trunk, then branch to their individual targets.\n * The overlapping trunk segments render as a single visible line.\n *\n * Junction points are placed outside subgraph boundaries so branches split\n * before entering a group, not inside it.\n *\n * Constraints: edges in a bundle must share the same style and have no labels.\n * Self-loops and backward edges (against the graph direction) are excluded.\n */\nexport function bundleEdgePaths(\n edges: PositionedEdge[],\n nodes: PositionedNode[],\n groups: PositionedGroup[],\n direction: Direction,\n): void {\n const nodeMap = new Map(nodes.map((n) => [n.id, n]))\n const processed = new Set<PositionedEdge>()\n\n const isLR = direction === 'LR'\n const isRL = direction === 'RL'\n const isBT = direction === 'BT'\n const isHorizontal = isLR || isRL\n\n // --- Fan-out: group edges by shared source ---\n const fanOutGroups = new Map<string, PositionedEdge[]>()\n for (const edge of edges) {\n if (edge.source === edge.target) continue\n const group = fanOutGroups.get(edge.source) ?? []\n fanOutGroups.set(edge.source, group)\n group.push(edge)\n }\n\n for (const [sourceId, group] of fanOutGroups) {\n if (group.length < 2) continue\n\n const style = group[0]!.style\n if (group.some((e) => e.label || e.style !== style)) continue\n\n const source = nodeMap.get(sourceId)\n if (!source) continue\n\n // Only bundle edges going in the forward direction. Collects the\n // resolved target node alongside each edge as it filters, rather than\n // re-querying nodeMap afterward — the two lookups can't be proven to\n // agree from a map() over a separately-filtered array.\n const targets: { edge: PositionedEdge; node: PositionedNode }[] = []\n for (const e of group) {\n const t = nodeMap.get(e.target)\n if (!t) continue\n const isForward = isLR\n ? t.x > source.x + source.width\n : isRL\n ? t.x + t.width < source.x\n : isBT\n ? t.y + t.height < source.y\n : t.y > source.y + source.height // TD/TB\n if (isForward) targets.push({ edge: e, node: t })\n }\n if (targets.length < 2) continue\n\n const srcCX = source.x + source.width / 2\n const srcCY = source.y + source.height / 2\n\n if (isHorizontal) {\n const exitX = isLR ? source.x + source.width : source.x\n const exitY = srcCY\n\n const nearestX = isLR\n ? Math.min(...targets.map((t) => t.node.x))\n : Math.max(...targets.map((t) => t.node.x + t.node.width))\n let junctionX = exitX + (nearestX - exitX) / 2\n junctionX = adjustJunctionForGroups(\n junctionX,\n srcCX,\n srcCY,\n groups,\n direction,\n )\n\n const bundled: { edge: PositionedEdge; points: Point[] }[] = []\n for (const { edge, node: target } of targets) {\n const entryX = isLR ? target.x : target.x + target.width\n const entryY = target.y + target.height / 2\n const points = [\n { x: exitX, y: exitY },\n { x: junctionX, y: exitY },\n { x: junctionX, y: entryY },\n { x: entryX, y: entryY },\n ]\n if (pathClearOfNodes(points, nodes, new Set([sourceId, target.id]))) {\n bundled.push({ edge, points })\n }\n }\n // One clear branch is not a bundle — leave the whole group as routed.\n if (bundled.length < 2) continue\n for (const { edge, points } of bundled) {\n edge.points = points\n processed.add(edge)\n }\n } else {\n const exitX = srcCX\n const exitY = isBT ? source.y : source.y + source.height\n\n const nearestY = isBT\n ? Math.max(...targets.map((t) => t.node.y + t.node.height))\n : Math.min(...targets.map((t) => t.node.y))\n let junctionY = exitY + (nearestY - exitY) / 2\n junctionY = adjustJunctionForGroups(\n junctionY,\n srcCX,\n srcCY,\n groups,\n direction,\n )\n\n const bundled: { edge: PositionedEdge; points: Point[] }[] = []\n for (const { edge, node: target } of targets) {\n const entryX = target.x + target.width / 2\n const entryY = isBT ? target.y + target.height : target.y\n const points = [\n { x: exitX, y: exitY },\n { x: exitX, y: junctionY },\n { x: entryX, y: junctionY },\n { x: entryX, y: entryY },\n ]\n if (pathClearOfNodes(points, nodes, new Set([sourceId, target.id]))) {\n bundled.push({ edge, points })\n }\n }\n // One clear branch is not a bundle — leave the whole group as routed.\n if (bundled.length < 2) continue\n for (const { edge, points } of bundled) {\n edge.points = points\n processed.add(edge)\n }\n }\n }\n\n // --- Fan-in: group edges by shared target (skip already-bundled edges) ---\n const fanInGroups = new Map<string, PositionedEdge[]>()\n for (const edge of edges) {\n if (processed.has(edge) || edge.source === edge.target) continue\n const group = fanInGroups.get(edge.target) ?? []\n fanInGroups.set(edge.target, group)\n group.push(edge)\n }\n\n for (const [targetId, group] of fanInGroups) {\n if (group.length < 2) continue\n\n const style = group[0]!.style\n if (group.some((e) => e.label || e.style !== style)) continue\n\n const target = nodeMap.get(targetId)\n if (!target) continue\n\n // Collects the resolved source node alongside each edge as it filters,\n // rather than re-querying nodeMap afterward (see the fan-out loop above\n // for why: a map() over a separately-filtered array can't be proven to\n // agree with the filter's own lookup).\n const sources: { edge: PositionedEdge; node: PositionedNode }[] = []\n for (const e of group) {\n const s = nodeMap.get(e.source)\n if (!s) continue\n const isForward = isLR\n ? s.x + s.width < target.x\n : isRL\n ? s.x > target.x + target.width\n : isBT\n ? s.y > target.y + target.height\n : s.y + s.height < target.y // TD/TB\n if (isForward) sources.push({ edge: e, node: s })\n }\n if (sources.length < 2) continue\n const tgtCX = target.x + target.width / 2\n const tgtCY = target.y + target.height / 2\n\n if (isHorizontal) {\n const entryX = isLR ? target.x : target.x + target.width\n const entryY = tgtCY\n\n const farthestX = isLR\n ? Math.max(...sources.map((s) => s.node.x + s.node.width))\n : Math.min(...sources.map((s) => s.node.x))\n let junctionX = farthestX + (entryX - farthestX) / 2\n junctionX = adjustJunctionForGroups(\n junctionX,\n tgtCX,\n tgtCY,\n groups,\n direction,\n )\n\n const bundled: { edge: PositionedEdge; points: Point[] }[] = []\n for (const { edge, node: src } of sources) {\n const exitX = isLR ? src.x + src.width : src.x\n const exitY = src.y + src.height / 2\n const points = [\n { x: exitX, y: exitY },\n { x: junctionX, y: exitY },\n { x: junctionX, y: entryY },\n { x: entryX, y: entryY },\n ]\n if (pathClearOfNodes(points, nodes, new Set([src.id, targetId]))) {\n bundled.push({ edge, points })\n }\n }\n // One clear branch is not a bundle — leave the whole group as routed.\n if (bundled.length < 2) continue\n for (const { edge, points } of bundled) {\n edge.points = points\n }\n } else {\n const entryX = tgtCX\n const entryY = isBT ? target.y + target.height : target.y\n\n const farthestY = isBT\n ? Math.min(...sources.map((s) => s.node.y))\n : Math.max(...sources.map((s) => s.node.y + s.node.height))\n let junctionY = farthestY + (entryY - farthestY) / 2\n junctionY = adjustJunctionForGroups(\n junctionY,\n tgtCX,\n tgtCY,\n groups,\n direction,\n )\n\n const bundled: { edge: PositionedEdge; points: Point[] }[] = []\n for (const { edge, node: src } of sources) {\n const exitX = src.x + src.width / 2\n const exitY = isBT ? src.y : src.y + src.height\n const points = [\n { x: exitX, y: exitY },\n { x: exitX, y: junctionY },\n { x: entryX, y: junctionY },\n { x: entryX, y: entryY },\n ]\n if (pathClearOfNodes(points, nodes, new Set([src.id, targetId]))) {\n bundled.push({ edge, points })\n }\n }\n // One clear branch is not a bundle — leave the whole group as routed.\n if (bundled.length < 2) continue\n for (const { edge, points } of bundled) {\n edge.points = points\n }\n }\n }\n}\n","/**\n * Shared ELK-edge-geometry extraction helpers.\n *\n * `from-elk.ts` (flowchart/state), `../class/layout.ts`, and\n * `../er/layout.ts` each independently walk an ELK edge's\n * `section.startPoint → bendPoints → endPoint` into a `Point[]`, plus\n * near-identical edge-label-position math. This module is the single\n * place that logic lives, so the three call sites can't drift.\n */\n\nimport type { ElkExtendedEdge } from 'elkjs'\nimport type { Point } from '@zombie-mermaid/core'\n\n/**\n * Walk an ELK edge's first routed section into a flat point path:\n * `startPoint → bendPoints → endPoint`.\n *\n * ELK can theoretically produce multiple sections per edge (for edges\n * split across hierarchy boundaries via ports), but all three call sites\n * only ever read `sections[0]` — hierarchical decomposition in this\n * codebase is handled by emitting separate ELK edges (see\n * `to-elk.ts`/`parseHopEdgeId` in `from-elk.ts`), not multi-section edges.\n *\n * `offsetX`/`offsetY` translate the section's coordinates into an\n * ancestor's coordinate space, for callers walking a nested ELK result\n * (`from-elk.ts`). Callers with a flat (non-hierarchical) ELK graph\n * (`class/layout.ts`, `er/layout.ts`) can omit them.\n *\n * Returns an empty array if the edge has no routed sections.\n */\nexport function extractEdgePoints(\n elkEdge: ElkExtendedEdge,\n offsetX = 0,\n offsetY = 0,\n): Point[] {\n const points: Point[] = []\n if (!elkEdge.sections || elkEdge.sections.length === 0) return points\n\n const section = elkEdge.sections[0]!\n points.push({\n x: section.startPoint.x + offsetX,\n y: section.startPoint.y + offsetY,\n })\n if (section.bendPoints) {\n for (const bp of section.bendPoints) {\n points.push({ x: bp.x + offsetX, y: bp.y + offsetY })\n }\n }\n points.push({\n x: section.endPoint.x + offsetX,\n y: section.endPoint.y + offsetY,\n })\n\n return points\n}\n\n/**\n * Compute an edge label's center position from ELK's placed label box\n * (`label.x/y` is the box's top-left corner).\n *\n * `offsetX`/`offsetY` translate into an ancestor's coordinate space, same\n * as `extractEdgePoints`.\n *\n * Returns `undefined` if ELK didn't place a label (no label on the edge,\n * or ELK left `x`/`y` unset).\n */\nexport function extractEdgeLabelPosition(\n elkEdge: ElkExtendedEdge,\n offsetX = 0,\n offsetY = 0,\n): Point | undefined {\n if (!elkEdge.labels || elkEdge.labels.length === 0) return undefined\n\n const label = elkEdge.labels[0]!\n if (label.x == null || label.y == null) return undefined\n\n return {\n x: label.x + (label.width ?? 0) / 2 + offsetX,\n y: label.y + (label.height ?? 0) / 2 + offsetY,\n }\n}\n","/**\n * Result conversion: ELK output → PositionedGraph.\n *\n * Split out of layout-engine.ts. This module handles turning ELK's layout\n * result back into our PositionedGraph format, including the post-processing\n * passes (layer alignment, edge bundling, shape-aware clipping) that clean\n * up ELK's raw output.\n */\n\nimport type { ElkNode, ElkExtendedEdge } from 'elkjs'\nimport type {\n MermaidGraph,\n MermaidSubgraph,\n PositionedGraph,\n PositionedNode,\n PositionedEdge,\n PositionedGroup,\n Point,\n} from '@zombie-mermaid/core'\nimport { ARROW_HEAD } from '../styles.ts'\nimport { clipEdgeToShape } from '../shape-clipping.ts'\nimport { DEFAULTS } from './constants.ts'\nimport { alignLayerNodes } from './layer-alignment.ts'\nimport { bundleEdgePaths } from './edge-bundling.ts'\nimport {\n extractEdgePoints,\n extractEdgeLabelPosition,\n} from './elk-adapter-utils.ts'\nimport { resolveNodeStyle } from '@zombie-mermaid/core'\n\n/** Margin routing info for cross-hierarchy edges */\ninterface MarginInfo {\n leftX: number\n rightX: number\n}\n\n// ============================================================================\n// Self-loop synthesis\n//\n// ELK has no native self-loop layout — to-elk.ts excludes edges where\n// `source === target` from the graph it hands to ELK entirely (see the\n// comments in `mermaidToElk`/`subgraphToElk`), so they never reach\n// ELK's own edge routing. Once node positions are final, this module\n// synthesizes a rounded loop bulging out from the node's right side —\n// approximated as a half-ellipse discretized into enough points to read\n// as smooth even under the default `linear` (straight-segment) curve\n// style — rather than letting ELK's generic orthogonal router draw a\n// degenerate zero-span polyline on top of the node.\n// ============================================================================\n\n/** Half-ellipse discretization steps; higher reads smoother as straight segments. */\nconst SELF_LOOP_SEGMENTS = 16\n/** Base horizontal reach of the loop bulge, in px. */\nconst SELF_LOOP_BASE_RADIUS = 28\n/** Additional reach per stacked self-loop on the same node, in px. */\nconst SELF_LOOP_STACK_SPACING = 18\n/** Vertical distance between the loop's two attach points, in px (clamped to node height). */\nconst SELF_LOOP_ATTACH_GAP = 28\n/**\n * Gap between the loop's apex and its label, in px. Wide enough that the\n * label's own background box (which paints over whatever it sits on top\n * of) doesn't swallow the loop's visible curve.\n */\nconst SELF_LOOP_LABEL_GAP = 10\n\n/**\n * Build the routed points and label position for one self-loop edge on\n * `node`, as a half-ellipse bulging out from the node's right side.\n *\n * `stackIndex` (0 for the first self-loop on this node, 1 for the second,\n * ...) widens each successive loop so multiple self-loops on the same node\n * nest rather than overlap.\n */\nfunction buildSelfLoopGeometry(\n node: PositionedNode,\n stackIndex: number,\n): { points: Point[]; labelPosition: Point } {\n const attachX = node.x + node.width\n const midY = node.y + node.height / 2\n const halfGap = Math.min(SELF_LOOP_ATTACH_GAP, node.height * 0.6) / 2\n const radius = SELF_LOOP_BASE_RADIUS + stackIndex * SELF_LOOP_STACK_SPACING\n\n // Half-ellipse from the upper attach point (t=0), out to the apex\n // (t=PI/2), back to the lower attach point (t=PI). Both attach points\n // sit exactly on the node's right edge, so shape-clipping (a no-op for\n // rectangle/rounded/stadium, the common cases) leaves them untouched.\n const points: Point[] = []\n for (let i = 0; i <= SELF_LOOP_SEGMENTS; i++) {\n const t = (Math.PI * i) / SELF_LOOP_SEGMENTS\n points.push({\n x: attachX + radius * Math.sin(t),\n y: midY - halfGap * Math.cos(t),\n })\n }\n\n return {\n points,\n labelPosition: { x: attachX + radius + SELF_LOOP_LABEL_GAP, y: midY },\n }\n}\n\n/**\n * Append a synthesized `PositionedEdge` for every self-loop\n * (`edge.source === edge.target`) in `graph.edges` onto `edges`.\n *\n * Must run after node positions are final (i.e. after `alignLayerNodes`) —\n * the loop geometry is derived directly from the node's laid-out\n * x/y/width/height, so computing it any earlier would have it drift out of\n * sync with a node that layer-alignment later shifts.\n */\nfunction synthesizeSelfLoopEdges(\n graph: MermaidGraph,\n nodeMap: Map<string, PositionedNode>,\n edges: PositionedEdge[],\n): void {\n const stackIndexByNode = new Map<string, number>()\n\n for (let index = 0; index < graph.edges.length; index++) {\n const edge = graph.edges[index]!\n if (edge.source !== edge.target) continue\n\n const node = nodeMap.get(edge.source)\n if (!node) continue\n\n const stackIndex = stackIndexByNode.get(node.id) ?? 0\n stackIndexByNode.set(node.id, stackIndex + 1)\n\n const { points, labelPosition } = buildSelfLoopGeometry(node, stackIndex)\n\n edges.push({\n source: edge.source,\n target: edge.target,\n label: edge.label,\n style: edge.style,\n hasArrowStart: edge.hasArrowStart,\n hasArrowEnd: edge.hasArrowEnd,\n points,\n labelPosition: edge.label ? labelPosition : undefined,\n inlineStyle: resolveEdgeStyle(index, graph),\n id: edge.id,\n animate: edge.animate,\n })\n }\n}\n\n/** Recursively flatten all group bounding boxes (including nested children) */\nfunction flattenGroupBounds(\n groups: PositionedGroup[],\n): Array<{ x: number; y: number; right: number; bottom: number }> {\n const bounds: Array<{ x: number; y: number; right: number; bottom: number }> =\n []\n for (const g of groups) {\n bounds.push({\n x: g.x,\n y: g.y,\n right: g.x + g.width,\n bottom: g.y + g.height,\n })\n bounds.push(...flattenGroupBounds(g.children))\n }\n return bounds\n}\n\n/**\n * Convert ELK layout result to our PositionedGraph format.\n */\nexport function elkToPositioned(\n elkResult: ElkNode,\n graph: MermaidGraph,\n mergeEdges: boolean = false,\n): PositionedGraph {\n const nodes: PositionedNode[] = []\n const edges: PositionedEdge[] = []\n const groups: PositionedGroup[] = []\n\n // Build set of subgraph IDs for distinguishing compound nodes from leaf nodes\n const subgraphIds = new Set<string>()\n for (const sg of graph.subgraphs) {\n collectAllSubgraphIds(sg, subgraphIds)\n }\n\n // Extract nodes and groups recursively\n extractNodesAndGroups(elkResult, graph, subgraphIds, nodes, groups, 0, 0)\n\n // Compute margin positions for cross-hierarchy edge routing.\n // Margins sit outside all group bounding boxes so edges don't cross through subgraphs.\n const allBounds = flattenGroupBounds(groups)\n const margins: MarginInfo | undefined =\n allBounds.length > 0\n ? {\n leftX: Math.min(...allBounds.map((b) => b.x)) - 20,\n rightX: Math.max(...allBounds.map((b) => b.right)) + 20,\n }\n : undefined\n\n // Extract edges recursively from all levels (root and subgraphs)\n // Edges are distributed to subgraphs for direction override to work,\n // so we need to collect them from all children with proper offsets\n extractEdgesRecursively(elkResult, graph, edges, 0, 0, margins)\n\n // Snap same-layer nodes to the same position along the flow axis.\n // ELK's orthogonal routing staggers nodes within a layer to create room for\n // edge bends, but this looks bad. We fix it by aligning layers, then let\n // edge bundling and clipping recalculate edge paths from corrected positions.\n alignLayerNodes(nodes, edges, graph.direction)\n\n // Synthesize self-loop edges now that node positions are final. These\n // were excluded from the graph handed to ELK (see to-elk.ts) since ELK\n // has no native self-loop layout; must run after alignLayerNodes so the\n // loop geometry is derived from each node's final, not pre-alignment,\n // position.\n const nodeMap = new Map(nodes.map((n) => [n.id, n]))\n synthesizeSelfLoopEdges(graph, nodeMap, edges)\n\n // Bundle fan-out/fan-in edge paths into shared trunks when mergeEdges is enabled\n if (mergeEdges) {\n bundleEdgePaths(edges, nodes, groups, graph.direction)\n }\n\n // Apply shape-aware edge clipping for non-rectangular shapes.\n // ELK treats all nodes as rectangles, so we need to clip edge endpoints\n // to the actual shape boundaries (e.g., diamond vertices).\n for (const edge of edges) {\n const sourceNode = nodeMap.get(edge.source)\n const targetNode = nodeMap.get(edge.target)\n\n if (sourceNode) {\n edge.points = clipEdgeToShape(edge.points, sourceNode, true)\n }\n if (targetNode) {\n edge.points = clipEdgeToShape(edge.points, targetNode, false)\n }\n }\n\n // Calculate final bounds including all edge points\n // ELK should include edges in its dimensions, but we verify and expand if needed\n let width = elkResult.width ?? 800\n let height = elkResult.height ?? 600\n const arrowMargin = ARROW_HEAD.width\n const padding = DEFAULTS.padding\n\n for (const edge of edges) {\n for (const p of edge.points) {\n width = Math.max(width, p.x + arrowMargin + padding)\n height = Math.max(height, p.y + arrowMargin + padding)\n }\n if (edge.labelPosition) {\n width = Math.max(width, edge.labelPosition.x + 60 + padding)\n height = Math.max(height, edge.labelPosition.y + 20 + padding)\n }\n }\n\n return {\n width,\n height,\n nodes,\n edges,\n groups,\n }\n}\n\n/**\n * Recursively extract positioned nodes and groups from ELK result.\n */\nfunction extractNodesAndGroups(\n elkNode: ElkNode,\n graph: MermaidGraph,\n subgraphIds: Set<string>,\n nodes: PositionedNode[],\n groups: PositionedGroup[],\n offsetX: number,\n offsetY: number,\n): void {\n if (!elkNode.children) return\n\n for (const child of elkNode.children) {\n const x = (child.x ?? 0) + offsetX\n const y = (child.y ?? 0) + offsetY\n const width = child.width ?? 0\n const height = child.height ?? 0\n\n if (subgraphIds.has(child.id)) {\n // This is a subgraph/group\n const childGroups: PositionedGroup[] = []\n\n // Recursively process children\n extractNodesAndGroups(child, graph, subgraphIds, nodes, childGroups, x, y)\n\n const mermaidSg = findSubgraph(graph.subgraphs, child.id)\n groups.push({\n id: child.id,\n label: mermaidSg?.label ?? '',\n x,\n y,\n width,\n height,\n children: childGroups,\n })\n } else {\n // This is a leaf node\n const mNode = graph.nodes.get(child.id)\n if (mNode) {\n // Resolve inline styles from nodeStyles map and classDefs\n const inlineStyle = resolveNodeStyle(child.id, graph)\n // Custom class name from `class A className` or `:::className` —\n // tracked separately from inlineStyle so it's still emitted onto the\n // rendered element even when the class has no matching classDef.\n const className = graph.classAssignments.get(child.id)\n\n nodes.push({\n id: child.id,\n label: mNode.label,\n shape: mNode.shape,\n x,\n y,\n width,\n height,\n inlineStyle,\n className,\n interaction: graph.interactions.get(child.id),\n })\n }\n\n // Also check for nested children (shouldn't happen for leaf nodes, but be safe)\n if (child.children && child.children.length > 0) {\n extractNodesAndGroups(child, graph, subgraphIds, nodes, groups, x, y)\n }\n }\n }\n}\n\n/**\n * Edge segment extracted from ELK result.\n * Used to combine the bridge and hop segments of a decomposed\n * cross-hierarchy edge back into one continuous path.\n */\ninterface EdgeSegment {\n edgeIndex: number\n points: Point[]\n labelPosition?: Point\n}\n\n/**\n * All segments belonging to one original edge, keyed by role:\n * - `bridge`: the sub-edge at the lowest common ancestor of source/target\n * (id `e{index}`) — the only segment for edges that don't cross a\n * subgraph boundary at all.\n * - `sourceHops`: source-side boundary crossings (id `e{index}_s{level}`),\n * keyed by ancestor-chain level. Walking from the innermost level\n * (closest to the source node) outward to the LCA.\n * - `targetHops`: target-side boundary crossings (id `e{index}_t{level}`),\n * keyed by ancestor-chain level. Walking from the LCA inward to the\n * innermost level (closest to the target node).\n * See packages/svg-renderer/src/layout-engine/to-elk.ts (mermaidToElk) for how these are produced.\n */\ninterface EdgeSegmentGroup {\n bridge?: EdgeSegment\n sourceHops: Map<number, EdgeSegment>\n targetHops: Map<number, EdgeSegment>\n}\n\n/** Parses an ELK edge id produced by the cross-hierarchy decomposition in to-elk.ts. */\nfunction parseHopEdgeId(\n id: string,\n):\n | { edgeIndex: number; role: 'bridge' }\n | { edgeIndex: number; role: 'source' | 'target'; level: number }\n | undefined {\n const bridgeMatch = /^e(\\d+)$/.exec(id)\n if (bridgeMatch) {\n return { edgeIndex: parseInt(bridgeMatch[1]!, 10), role: 'bridge' }\n }\n const hopMatch = /^e(\\d+)_([st])(\\d+)$/.exec(id)\n if (hopMatch) {\n return {\n edgeIndex: parseInt(hopMatch[1]!, 10),\n role: hopMatch[2] === 's' ? 'source' : 'target',\n level: parseInt(hopMatch[3]!, 10),\n }\n }\n return undefined\n}\n\n/**\n * Calculate the midpoint along a polyline path.\n * Walks the path to find the point at half the total length.\n */\nfunction calculatePathMidpoint(points: Point[]): Point {\n if (points.length === 0) return { x: 0, y: 0 }\n if (points.length === 1) return points[0]!\n\n // Calculate total length\n let totalLength = 0\n for (let i = 1; i < points.length; i++) {\n const dx = points[i]!.x - points[i - 1]!.x\n const dy = points[i]!.y - points[i - 1]!.y\n totalLength += Math.sqrt(dx * dx + dy * dy)\n }\n\n // Walk to halfway point\n let remaining = totalLength / 2\n for (let i = 1; i < points.length; i++) {\n const dx = points[i]!.x - points[i - 1]!.x\n const dy = points[i]!.y - points[i - 1]!.y\n const segLen = Math.sqrt(dx * dx + dy * dy)\n if (remaining <= segLen) {\n const t = remaining / segLen\n return {\n x: points[i - 1]!.x + t * dx,\n y: points[i - 1]!.y + t * dy,\n }\n }\n remaining -= segLen\n }\n\n return points[points.length - 1]!\n}\n\n/**\n * Recursively extract edges from ELK result including those inside subgraphs.\n * Edges are distributed to subgraphs for direction override to work,\n * so we need to collect them from all levels with proper coordinate offsets.\n *\n * For hierarchical edges (cross-hierarchy with ports), combines external and\n * internal segments into a single continuous edge path.\n */\nfunction extractEdgesRecursively(\n elkNode: ElkNode,\n graph: MermaidGraph,\n edges: PositionedEdge[],\n offsetX: number,\n offsetY: number,\n margins?: MarginInfo,\n): void {\n // First pass: collect all edge segments.\n //\n // Under `hierarchyHandling: INCLUDE_CHILDREN` (see to-elk.ts — used when no\n // subgraph has a direction override), ELK is free to leave a\n // cross-hierarchy edge in an *ancestor* node's `edges` array while\n // reporting its sections/labels in the coordinate space of the deeper\n // container named by the edge's own (untyped-in-elkjs) `container`\n // property. `collectEdgeSegments`'s tree walk assumes an edge's\n // coordinates match the array it was found in, which breaks for exactly\n // this case — the edge lands at the *ancestor's* offset instead of the\n // declared container's, shifting nested-subgraph edges/labels off their\n // node boundaries toward the root. Pre-computing every container's\n // accumulated root offset here lets `collectEdgeSegments` prefer the\n // edge's declared container when present, falling back to the owning\n // array's offset (the previous behavior) otherwise — see\n // zombie-mermaid#1145, ported from a fix by Galen Suen in\n // lukilabs/beautiful-mermaid#152.\n const containerOffsets = new Map<string, Point>()\n collectContainerOffsets(elkNode, containerOffsets, 0, 0)\n const segments = new Map<number, EdgeSegmentGroup>()\n collectEdgeSegments(elkNode, segments, 0, 0, containerOffsets)\n\n // Track margin-routed edge count for spacing offsets\n let marginEdgeIndex = 0\n\n // Second pass: combine segments and create positioned edges\n for (const [edgeIndex, seg] of segments) {\n const originalEdge = graph.edges[edgeIndex]\n if (!originalEdge) continue\n\n // Combine points from all segments in path order:\n // source-side hops (innermost → outermost, i.e. descending level)\n // → bridge (the sub-edge at the lowest common ancestor)\n // → target-side hops (outermost → innermost, i.e. ascending level)\n // Edges that don't cross any subgraph boundary have only a bridge\n // segment. Edges crossing exactly one boundary have a bridge plus a\n // single hop on one side — this is the shape the previous single-hop\n // implementation always produced. Deeper nesting simply adds more hops\n // on either side, each joined at the shared boundary point.\n const allPoints: Point[] = []\n const appendSegment = (segment: EdgeSegment | undefined): void => {\n if (!segment || segment.points.length === 0) return\n if (allPoints.length > 0) {\n // Skip first point to avoid duplicating the shared boundary point.\n allPoints.push(...segment.points.slice(1))\n } else {\n allPoints.push(...segment.points)\n }\n }\n\n const sourceLevels = [...seg.sourceHops.keys()].sort((a, b) => b - a)\n for (const level of sourceLevels) appendSegment(seg.sourceHops.get(level))\n appendSegment(seg.bridge)\n const targetLevels = [...seg.targetHops.keys()].sort((a, b) => a - b)\n for (const level of targetLevels) appendSegment(seg.targetHops.get(level))\n\n // Label position: use ELK's inline label position (on-edge with collision avoidance)\n // Fall back to midpoint for hierarchical edges or when ELK position unavailable\n let labelPosition: Point | undefined\n if (originalEdge.label && allPoints.length >= 2) {\n const elkLabelPos = seg.bridge?.labelPosition\n labelPosition = elkLabelPos ?? calculatePathMidpoint(allPoints)\n }\n\n // Ensure all edge segments are orthogonal (horizontal or vertical only).\n // In SEPARATE hierarchy mode, ELK may produce diagonal segments for\n // cross-hierarchy edges where it only returns start/end points without\n // proper orthogonal bend points.\n // When margins are available, route through the diagram margins instead\n // of Z-paths through the middle (which cross through subgraphs).\n const orthogonalPoints = orthogonalizeEdgePoints(\n allPoints,\n margins,\n marginEdgeIndex,\n )\n if (orthogonalPoints !== allPoints) {\n marginEdgeIndex++\n }\n\n // Recalculate label position for margin-routed edges\n if (\n originalEdge.label &&\n orthogonalPoints !== allPoints &&\n orthogonalPoints.length >= 2\n ) {\n labelPosition = calculatePathMidpoint(orthogonalPoints)\n }\n\n edges.push({\n source: originalEdge.source,\n target: originalEdge.target,\n label: originalEdge.label,\n style: originalEdge.style,\n hasArrowStart: originalEdge.hasArrowStart,\n hasArrowEnd: originalEdge.hasArrowEnd,\n points: orthogonalPoints,\n labelPosition,\n inlineStyle: resolveEdgeStyle(edgeIndex, graph),\n id: originalEdge.id,\n animate: originalEdge.animate,\n })\n }\n}\n\n/**\n * Build a map from every ELK node's own id to its accumulated root-relative\n * offset, by walking the same `elkNode.children` tree `collectEdgeSegments`\n * walks. Used to resolve a cross-hierarchy edge's declared `container`\n * (an ELK output property not modeled in elkjs's types) to the offset of\n * the container ELK actually reported its coordinates in, rather than the\n * offset of whichever ancestor array the edge object happened to be left\n * in — see the comment on `extractEdgesRecursively`'s call site.\n */\nfunction collectContainerOffsets(\n elkNode: ElkNode,\n offsets: Map<string, Point>,\n offsetX: number,\n offsetY: number,\n): void {\n offsets.set(elkNode.id, { x: offsetX, y: offsetY })\n for (const child of elkNode.children ?? []) {\n // `x`/`y` are optional in elkjs's types, but ELK always assigns both to\n // every laid-out child node — the `?? 0` fallback mirrors the same\n // defensive pattern already used for this walk in `collectEdgeSegments`\n // below, and is equally unreachable from real ELK output.\n /* v8 ignore next */\n const childX = child.x ?? 0\n /* v8 ignore next */\n const childY = child.y ?? 0\n collectContainerOffsets(child, offsets, offsetX + childX, offsetY + childY)\n }\n}\n\n/**\n * Post-process edge points to ensure all segments are purely orthogonal.\n *\n * When ELK uses SEPARATE hierarchy handling (required for subgraph direction\n * overrides), cross-hierarchy edges may only get start/end coordinates without\n * intermediate bend points, producing diagonal lines.\n *\n * When margins are provided, routes diagonal segments through the left or right\n * margin of the diagram (outside all subgraphs). Alternates sides and adds\n * spacing offsets to prevent overlapping parallel edges.\n *\n * Without margins, falls back to Z-path through the vertical midpoint.\n *\n * Returns the original array reference (identity) if no changes were needed,\n * so callers can detect whether routing was applied.\n */\nfunction orthogonalizeEdgePoints(\n points: Point[],\n margins?: MarginInfo,\n edgeIndex: number = 0,\n): Point[] {\n if (points.length < 2) return points\n\n // Check if any segment needs orthogonalization\n let needsWork = false\n for (let i = 1; i < points.length; i++) {\n const dx = Math.abs(points[i]!.x - points[i - 1]!.x)\n const dy = Math.abs(points[i]!.y - points[i - 1]!.y)\n if (dx > 1 && dy > 1) {\n needsWork = true\n break\n }\n }\n if (!needsWork) return points\n\n const EDGE_SPACING = 12\n const result: Point[] = [points[0]!]\n\n for (let i = 1; i < points.length; i++) {\n const prev = result[result.length - 1]!\n const curr = points[i]!\n const dx = Math.abs(curr.x - prev.x)\n const dy = Math.abs(curr.y - prev.y)\n\n if (dx > 1 && dy > 1) {\n if (margins) {\n // Margin routing: exit horizontally → travel vertically along margin → enter horizontally\n // Alternate left/right margins and offset for parallel edge spacing\n const useRight = edgeIndex % 2 === 0\n const offset = Math.floor(edgeIndex / 2) * EDGE_SPACING\n const marginX = useRight\n ? margins.rightX + offset\n : margins.leftX - offset\n\n result.push({ x: marginX, y: prev.y })\n result.push({ x: marginX, y: curr.y })\n } else {\n // Fallback: Z-path through vertical midpoint\n const midY = (prev.y + curr.y) / 2\n result.push({ x: prev.x, y: midY })\n result.push({ x: curr.x, y: midY })\n }\n }\n\n result.push(curr)\n }\n\n return result\n}\n\n/**\n * Recursively collect edge segments from ELK result.\n */\nfunction collectEdgeSegments(\n elkNode: ElkNode,\n segments: Map<number, EdgeSegmentGroup>,\n offsetX: number,\n offsetY: number,\n containerOffsets: Map<string, Point>,\n): void {\n if (elkNode.edges) {\n for (const elkEdge of elkNode.edges) {\n // Parse edge ID: \"e{index}\" (bridge), \"e{index}_s{level}\" (source-side\n // hop), or \"e{index}_t{level}\" (target-side hop) — see to-elk.ts.\n const parsed = parseHopEdgeId(elkEdge.id)\n if (!parsed) continue\n const { edgeIndex } = parsed\n\n // Prefer the edge's declared container offset (see\n // `collectContainerOffsets`'s comment) over the owning array's\n // offset. `container` and `containerOffsets.get(container)` were\n // verified (by direct instrumentation against real elk.bundled.js\n // output, across both flat and multiply-nested subgraphs) to always\n // be defined in practice — ELK tags every edge with its container,\n // even at the root, and `collectContainerOffsets` walks the same\n // tree so every container id it can name is already in the map. The\n // `container?`/`?? offset{X,Y}` fallbacks below exist only because\n // `container` is untyped in elkjs and `Map.get` is typed to allow a\n // miss; kept as defensive narrowing rather than assumed impossible.\n const container = (elkEdge as ElkExtendedEdge & { container?: string })\n .container\n /* v8 ignore next */\n const containerOffset = container\n ? containerOffsets.get(container)\n : undefined\n /* v8 ignore next */\n const edgeOffsetX = containerOffset?.x ?? offsetX\n /* v8 ignore next */\n const edgeOffsetY = containerOffset?.y ?? offsetY\n\n // Extract points and label position\n const points = extractEdgePoints(elkEdge, edgeOffsetX, edgeOffsetY)\n const labelPosition = extractEdgeLabelPosition(\n elkEdge,\n edgeOffsetX,\n edgeOffsetY,\n )\n\n // Store segment\n const seg: EdgeSegmentGroup = segments.get(edgeIndex) ?? {\n sourceHops: new Map(),\n targetHops: new Map(),\n }\n segments.set(edgeIndex, seg)\n const segment: EdgeSegment = { edgeIndex, points, labelPosition }\n\n if (parsed.role === 'bridge') {\n seg.bridge = segment\n } else if (parsed.role === 'source') {\n seg.sourceHops.set(parsed.level, segment)\n } else {\n seg.targetHops.set(parsed.level, segment)\n }\n }\n }\n\n // Recurse into children with accumulated offset\n if (elkNode.children) {\n for (const child of elkNode.children) {\n collectEdgeSegments(\n child,\n segments,\n offsetX + (child.x ?? 0),\n offsetY + (child.y ?? 0),\n containerOffsets,\n )\n }\n }\n}\n\n/** Find a subgraph by ID in a nested structure */\nfunction findSubgraph(\n subgraphs: MermaidSubgraph[],\n id: string,\n): MermaidSubgraph | undefined {\n for (const sg of subgraphs) {\n if (sg.id === id) return sg\n const found = findSubgraph(sg.children, id)\n if (found) return found\n }\n return undefined\n}\n\n/** Recursively collect all subgraph IDs */\nfunction collectAllSubgraphIds(sg: MermaidSubgraph, out: Set<string>): void {\n out.add(sg.id)\n for (const child of sg.children) {\n collectAllSubgraphIds(child, out)\n }\n}\n\n/**\n * Resolve inline styles for an edge from linkStyles map.\n * Default link style is applied first, then index-specific overrides.\n */\nfunction resolveEdgeStyle(\n edgeIndex: number,\n graph: MermaidGraph,\n): Record<string, string> | undefined {\n let result: Record<string, string> | undefined\n\n const defaultStyle = graph.linkStyles.get('default')\n if (defaultStyle) {\n result = { ...defaultStyle }\n }\n\n const indexStyle = graph.linkStyles.get(edgeIndex)\n if (indexStyle) {\n result = result ? { ...result, ...indexStyle } : { ...indexStyle }\n }\n\n return result\n}\n","/**\n * Layout engine for zombie-mermaid (ELK.js based).\n *\n * Converts MermaidGraph to ELK's JSON format, runs layout, and converts\n * the result back to PositionedGraph. This is the core layout engine used\n * by all graph-based diagram types (flowcharts, state, ER, class).\n *\n * ELK (Eclipse Layout Kernel) features:\n * - Native orthogonal edge routing (no post-processing needed)\n * - Proper handling of compound nodes (subgraphs)\n * - Support for disconnected graphs\n * - Direction overrides per subgraph\n * - Sophisticated algorithms for complex graphs\n *\n * Uses elk.bundled.js (pure synchronous JS, no WASM/Workers).\n * Safe for Electron, Node, and browser environments.\n *\n * This file is the public entry point (layoutGraphSync, convertToElkFormat).\n * The implementation is split across src/layout-engine/ by pipeline stage:\n * - to-elk.ts MermaidGraph → ELK JSON input\n * - from-elk.ts ELK result → PositionedGraph, plus shape clipping\n * - layer-alignment.ts post-process: snap same-layer nodes to a uniform position\n * - edge-bundling.ts post-process: merge fan-out/fan-in edges into shared trunks\n * See packages/svg-renderer/src/layout-engine/constants.ts for the shared DEFAULTS used across stages.\n */\n\nimport type { ElkNode } from 'elkjs'\nimport type {\n MermaidGraph,\n PositionedGraph,\n FlowchartRenderOptions,\n} from '@zombie-mermaid/core'\nimport { elkLayoutSync } from './elk-instance.ts'\nimport { resolveFontSizes } from './styles.ts'\nimport { DEFAULTS } from './layout-engine/constants.ts'\nimport { mermaidToElk } from './layout-engine/to-elk.ts'\nimport { elkToPositioned } from './layout-engine/from-elk.ts'\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Lay out a parsed MermaidGraph using ELK.js (synchronous).\n * Returns a fully positioned graph ready for rendering.\n */\nexport function layoutGraphSync(\n graph: MermaidGraph,\n options: FlowchartRenderOptions = {},\n): PositionedGraph {\n const opts = {\n ...DEFAULTS,\n ...options,\n fontSizes: resolveFontSizes(options.fontSizes),\n }\n const elkGraph = mermaidToElk(graph, opts)\n const result = elkLayoutSync(elkGraph, options.layoutCache)\n return elkToPositioned(result, graph, opts.mergeEdges)\n}\n\n/**\n * Convert MermaidGraph to ELK format (for benchmarking conversion overhead).\n */\nexport function convertToElkFormat(\n graph: MermaidGraph,\n options: FlowchartRenderOptions = {},\n): ElkNode {\n const opts = {\n ...DEFAULTS,\n ...options,\n fontSizes: resolveFontSizes(options.fontSizes),\n }\n return mermaidToElk(graph, opts)\n}\n","import type {\n PositionedGraph,\n PositionedNode,\n PositionedEdge,\n PositionedGroup,\n Point,\n DiagramColors,\n SvgEmitOptions,\n CurveStyle,\n} from '@zombie-mermaid/core'\nimport {\n svgOpenTag,\n buildStyleBlock,\n styleOpenTag,\n getReadableTextColor,\n measureMultilineText,\n renderMultilineText,\n renderMultilineTextWithBackground,\n escapeXml,\n escapeAttr,\n safeHref,\n sanitizeClassName,\n f,\n} from '@zombie-mermaid/core'\nimport type { FontSizes } from './styles.ts'\nimport {\n FONT_SIZES,\n FONT_WEIGHTS,\n STROKE_WIDTHS,\n ARROW_HEAD,\n} from './styles.ts'\nimport { pointsToPath } from './edge-curves.ts'\n\n// ============================================================================\n// SVG renderer — converts a PositionedGraph into an SVG string.\n//\n// Pure string concatenation, no DOM manipulation.\n// Renders back-to-front: groups → edges → arrow heads → edge labels → nodes → node labels.\n//\n// All colors are referenced via CSS custom properties (var(--_xxx)) defined\n// in the <style> block. The caller provides bg/fg (+ optional enrichment\n// colors) via DiagramColors, which are set as inline CSS variables on the\n// <svg> tag. See packages/core/src/theme.ts for the full variable system.\n//\n// Style spec:\n// - All corners rx=0 ry=0 (sharp)\n// - Stroke widths: outer box 1px, inner box 0.75px, connectors 0.75px\n// - Arrow heads: filled triangles, 8px wide × 4.8px tall\n// - Dashed edges: stroke-dasharray=\"4 4\"\n// - Font: Inter with weight per element type\n// ============================================================================\n\n/**\n * Render a positioned graph as an SVG string.\n *\n * @param colors - DiagramColors with bg/fg and optional enrichment variables.\n * These are set as CSS custom properties on the <svg> tag.\n * All element colors reference derived --_xxx variables.\n * @param transparent - If true, renders with transparent background.\n * @param embedSource - Original diagram source to stamp onto the root `<svg>`\n * as `data-src` (from `options.embedSource`). Omitted\n * when the option is off.\n * @param animationEnabled - Whether `e1@{ animate: true }` edges actually\n * animate (from `options.interactivity === 'full'`,\n * see `resolveAnimationEnabled` in\n * src/diagram-registry.ts).\n * Default true — preserves the previously-ungated\n * behavior for callers who don't pass it.\n * @param linksEnabled - Whether `click`-based `<a href>` links and `<title>`\n * tooltips render (from\n * `options.interactivity !== 'none'`, see\n * `resolveLinksEnabled` in src/diagram-registry.ts). Default true\n * — preserves the previously-ungated behavior for\n * callers who don't pass it.\n * @param title - Accessible name (from `options.title`). See svgOpenTag() in\n * packages/core/src/theme.ts.\n * @param decorative - Marks the SVG decorative (from `options.decorative`).\n * @param emit - Strict-CSP controls (from `options.nonce` /\n * `options.styleAttribute`, see #216): a `nonce` for every\n * `<style>` element, and whether the root `style=\"…\"`\n * attribute is emitted at all. Default: no nonce, attribute on.\n */\nexport function renderSvg(\n graph: PositionedGraph,\n colors: DiagramColors,\n font: string = 'Inter',\n transparent: boolean = false,\n fontSizes: FontSizes = FONT_SIZES,\n curve: CurveStyle = 'linear',\n embedSource?: string,\n animationEnabled: boolean = true,\n linksEnabled: boolean = true,\n title?: string,\n decorative?: boolean,\n emit: SvgEmitOptions = {},\n): string {\n const parts: string[] = []\n\n // See #239: a click-based link renders as a focusable <a href> inside the\n // SVG, which role=\"img\"/aria-hidden would hide from assistive tech while\n // leaving it Tab-reachable — svgOpenTag forces no root role in that case.\n // Uses the same safeHref() check renderNode uses to decide whether an <a>\n // is actually emitted — a rejected scheme or control character means no\n // link renders, so it shouldn't affect the SVG's accessibility semantics.\n // Gated by linksEnabled too: `interactivity: 'none'` strips the <a> below,\n // so it shouldn't count toward this decision either.\n const hasInteractiveLinks =\n linksEnabled &&\n graph.nodes.some((n) => Boolean(safeHref(n.interaction?.href)))\n\n // SVG root with CSS variables + style block + defs\n parts.push(\n withDataSrc(\n svgOpenTag(\n graph.width,\n graph.height,\n colors,\n transparent,\n title,\n decorative,\n hasInteractiveLinks,\n emit.styleAttribute,\n ),\n embedSource,\n ),\n )\n parts.push(buildStyleBlock(font, false, emit.nonce))\n // Keyframes for animated edges (`e1@{ animate: true }`). Emitted only when\n // an animated edge exists and animation is enabled, so an ordinary diagram\n // (or one rendered with `interactivity: 'none'`) gains no extra markup.\n if (animationEnabled && graph.edges.some((e) => e.animate)) {\n parts.push(edgeAnimationStyle(emit.nonce))\n }\n parts.push('<defs>')\n parts.push(arrowMarkerDefs())\n // Per-color arrow markers for edges with custom stroke via linkStyle\n const customStrokeColors = new Set<string>()\n for (const edge of graph.edges) {\n if (edge.inlineStyle?.stroke) {\n customStrokeColors.add(edge.inlineStyle.stroke)\n }\n }\n for (const color of customStrokeColors) {\n parts.push(arrowMarkerDefsForColor(color))\n }\n parts.push('</defs>')\n\n // 1. Subgraph backgrounds (group rectangles with header bands)\n for (const group of graph.groups) {\n parts.push(renderGroup(group, font, fontSizes))\n }\n\n // 2. Edges (paths — rendered behind nodes)\n // Each edge is a <path> with semantic data-* attributes\n for (const edge of graph.edges) {\n parts.push(renderEdge(edge, curve, animationEnabled))\n }\n\n // 3. Edge labels (positioned at midpoint of edge)\n // Each label is wrapped in <g class=\"edge-label\">\n for (const edge of graph.edges) {\n if (edge.label) {\n parts.push(renderEdgeLabel(edge, font, fontSizes))\n }\n }\n\n // 4. Nodes (shape + label wrapped in <g class=\"node\">)\n for (const node of graph.nodes) {\n parts.push(renderNode(node, font, fontSizes, linksEnabled))\n }\n\n parts.push('</svg>')\n\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Arrow marker definitions\n// ============================================================================\n\n/**\n * Keyframes for `e1@{ animate: true }` edges — a marching-ants dash.\n *\n * CSS animation rather than SMIL: SMIL is deprecated in browsers, while a\n * CSS `@keyframes` block inside the SVG animates in browsers and is simply\n * ignored by static rasterizers, which render the first frame. The\n * `prefers-reduced-motion` guard stops the animation for users who have asked\n * the system for less movement — the edge still renders, just still.\n *\n * A second `<style>` element, so it takes the same `nonce` as the theme\n * block (see `styleOpenTag`) — under a nonce-based CSP it would otherwise be\n * the one un-nonced element on the page and get silently dropped.\n */\nfunction edgeAnimationStyle(nonce?: string): string {\n return [\n styleOpenTag(nonce),\n ' @keyframes zm-edge-dash { to { stroke-dashoffset: -28; } }',\n ' .edge-animated { animation: zm-edge-dash 1s linear infinite; }',\n ' @media (prefers-reduced-motion: reduce) {',\n ' .edge-animated { animation: none; }',\n ' }',\n '</style>',\n ].join('\\n')\n}\n\n/**\n * Reusable arrow head markers — both forward (end) and reverse (start) variants.\n * Arrow color uses the var(--_arrow) CSS variable.\n */\nfunction arrowMarkerDefs(): string {\n return arrowMarkerPair('var(--_arrow)', '')\n}\n\n/**\n * Arrow markers tinted to a specific color (for linkStyle stroke overrides).\n * IDs are suffixed with a sanitized color string to avoid collisions.\n */\nfunction arrowMarkerDefsForColor(color: string): string {\n return arrowMarkerPair(escapeAttr(color), f`-${markerSuffix(color)}`)\n}\n\n/**\n * Build the forward (marker-end) + reverse (marker-start) arrow-head marker pair.\n *\n * Both heads share one polygon. The reverse head differs only by\n * orient=\"auto-start-reverse\", which flips it 180° to point back out of\n * the start node — so the polygon must NOT be pre-reversed. Reversing both is\n * a double reversal: the head points into the line and vanishes in\n * librsvg/Inkscape/browsers.\n */\nfunction arrowMarkerPair(color: string, idSuffix: string): string {\n const w = ARROW_HEAD.width\n const h = ARROW_HEAD.height\n // Pull arrowhead back slightly (refX = w - 1) to prevent clipping at node boundaries.\n const refX = w - 1\n // Both fill and a thin stroke for better definition at small sizes.\n const style = f`fill=\"${color}\" stroke=\"${color}\" stroke-width=\"0.75\" stroke-linejoin=\"round\"`\n const polygon = f`<polygon points=\"0 0, ${w} ${h / 2}, 0 ${h}\" ${style} />`\n const marker = (id: string, orient: string) =>\n f` <marker id=\"${id}\" markerWidth=\"${w}\" markerHeight=\"${h}\" refX=\"${refX}\" refY=\"${h / 2}\" orient=\"${orient}\">` +\n f`\\n ${polygon}` +\n `\\n </marker>`\n return (\n marker(f`arrowhead${idSuffix}`, 'auto') +\n '\\n' +\n marker(f`arrowhead-start${idSuffix}`, 'auto-start-reverse')\n )\n}\n\n/** Sanitize a color value into a collision-free SVG ID suffix.\n * Non-alphanumeric chars are hex-encoded so distinct inputs never collapse\n * (e.g. \"var(--line-1)\" → \"var28--line2d129\", \"var(--line1)\" → \"var28--line129\"). */\nfunction markerSuffix(color: string): string {\n return color.replace(/[^a-zA-Z0-9]/g, (ch) => ch.charCodeAt(0).toString(16))\n}\n\n// ============================================================================\n// Group rendering (subgraph backgrounds)\n// ============================================================================\n\nfunction renderGroup(\n group: PositionedGroup,\n font: string,\n fontSizes: FontSizes,\n): string {\n const headerHeight = fontSizes.groupHeader + 16\n const parts: string[] = []\n\n // Opening <g> with semantic attributes for subgraph identification\n // data-id: original Mermaid subgraph ID\n // data-label: display label (may differ from ID)\n parts.push(\n f`<g class=\"subgraph\" data-id=\"${escapeAttr(group.id)}\" data-label=\"${escapeAttr(group.label)}\">`,\n )\n\n // Outer rectangle\n parts.push(\n f` <rect x=\"${group.x}\" y=\"${group.y}\" width=\"${group.width}\" height=\"${group.height}\" ` +\n f`rx=\"0\" ry=\"0\" fill=\"var(--_group-fill)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n\n // Header band\n parts.push(\n f` <rect x=\"${group.x}\" y=\"${group.y}\" width=\"${group.width}\" height=\"${headerHeight}\" ` +\n f`rx=\"0\" ry=\"0\" fill=\"var(--_group-hdr)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n\n // Header label (supports multi-line via <br> tags)\n parts.push(\n ' ' +\n renderMultilineText(\n group.label,\n group.x + 12,\n group.y + headerHeight / 2,\n fontSizes.groupHeader,\n f`font-size=\"${fontSizes.groupHeader}\" font-weight=\"${FONT_WEIGHTS.groupHeader}\" fill=\"var(--_text-sec)\"`,\n ),\n )\n\n // Render nested groups recursively (inside this group)\n for (const child of group.children) {\n parts.push(renderGroup(child, font, fontSizes))\n }\n\n parts.push('</g>')\n\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Edge rendering\n// ============================================================================\n\nfunction renderEdge(\n edge: PositionedEdge,\n curve: CurveStyle,\n animationEnabled: boolean = true,\n): string {\n if (edge.points.length < 2) return ''\n\n // `interactivity: 'none'` strips motion: the edge still renders (id,\n // data-* attributes, arrowheads) but never gets the animated class or dash.\n const animate = animationEnabled && (edge.animate ?? false)\n\n /*\n * A curved edge must be a <path>; only a path can express the\n * interpolations Mermaid's `flowchart.curve` selects.\n *\n * The default (`linear`) deliberately keeps emitting <polyline>. A path of\n * straight `L` segments would be geometrically identical, but changing the\n * element for every diagram would break any consumer selecting\n * `polyline.edge` — for no benefit to a diagram that asked for no curve.\n * So the element changes only when the author opts into a curve.\n */\n const curved = curve !== 'linear'\n\n /*\n * An invisible link (`A ~~~ B`) still occupies its layout slot — that is\n * the whole point of the syntax — so the element is emitted with its data\n * attributes intact and only its paint suppressed. Omitting the element\n * entirely would lose it from DOM inspection and from `data-style` queries.\n */\n const invisible = edge.style === 'invisible'\n\n const dashArray = edge.style === 'dotted' ? ' stroke-dasharray=\"4 4\"' : ''\n const baseStrokeWidth =\n edge.style === 'thick'\n ? STROKE_WIDTHS.connector * 2\n : STROKE_WIDTHS.connector\n const strokeColor = invisible\n ? 'none'\n : escapeAttr(edge.inlineStyle?.stroke ?? 'var(--_line)')\n const strokeWidth = escapeAttr(\n edge.inlineStyle?.['stroke-width'] ?? String(baseStrokeWidth),\n )\n\n // Build marker attributes based on arrow direction flags\n // Use color-specific markers when edge has a custom stroke from linkStyle\n const suffix = edge.inlineStyle?.stroke\n ? f`-${markerSuffix(edge.inlineStyle.stroke)}`\n : ''\n let markers = ''\n if (!invisible) {\n if (edge.hasArrowEnd) markers += f` marker-end=\"url(#arrowhead${suffix})\"`\n if (edge.hasArrowStart)\n markers += f` marker-start=\"url(#arrowhead-start${suffix})\"`\n }\n\n // Semantic data attributes for edge identification and inspection:\n // - class=\"edge\": CSS targeting and type identification\n // - data-from/data-to: source and target node IDs\n // - data-style: edge style (solid, dotted, thick)\n // - data-arrow-start/end: arrow presence flags\n // - data-label: edge label if present (for quick lookup without traversing DOM)\n const dataAttrs = [\n // An animated edge (`e1@{ animate: true }`, with animation enabled)\n // carries an extra class; its keyframes live in the shared style block.\n f`class=\"edge${animate ? ' edge-animated' : ''}\"`,\n f`data-from=\"${escapeAttr(edge.source)}\"`,\n f`data-to=\"${escapeAttr(edge.target)}\"`,\n f`data-style=\"${edge.style}\"`,\n f`data-arrow-start=\"${edge.hasArrowStart}\"`,\n f`data-arrow-end=\"${edge.hasArrowEnd}\"`,\n ]\n if (edge.label) {\n dataAttrs.push(f`data-label=\"${escapeAttr(edge.label)}\"`)\n }\n if (edge.id) {\n // Mermaid edge id (`A e1@--> B`), used to target the edge from CSS and\n // to attach the animation below.\n dataAttrs.push(f`data-id=\"${escapeAttr(edge.id)}\"`)\n }\n\n // Marching ants need a dash pattern to march; supply one only when the\n // edge's own style hasn't already set stroke-dasharray.\n const animatedDash = animate && !dashArray ? ' stroke-dasharray=\"8 6\"' : ''\n\n const geometry = curved\n ? f`d=\"${pointsToPath(edge.points, curve)}\"`\n : f`points=\"${pointsToPolylinePath(edge.points)}\"`\n\n return (\n f`<${curved ? 'path' : 'polyline'} ${dataAttrs.join(' ')} ${geometry} ` +\n f`fill=\"none\" stroke=\"${strokeColor}\" ` +\n f`stroke-width=\"${strokeWidth}\"${dashArray}${animatedDash}${markers} />`\n )\n}\n\n/** Convert points to SVG polyline points attribute: \"x1,y1 x2,y2 ...\" */\nfunction pointsToPolylinePath(points: Point[]): string {\n return points.map((p) => f`${p.x},${p.y}`).join(' ')\n}\n\n// `_font` isn't read here but is kept to match the `(entity, font)` signature\n// threaded through the rest of the render* functions in this file.\nfunction renderEdgeLabel(\n edge: PositionedEdge,\n _font: string,\n fontSizes: FontSizes,\n): string {\n // Only called when edge.label is set (see call site), but narrow it here\n // too rather than trusting that invariant across the function boundary.\n if (!edge.label) return ''\n const label = edge.label\n\n // Use layout-computed label position when available (layout-aware, avoids collisions).\n // Fall back to geometric midpoint of the edge polyline.\n const mid = edge.labelPosition ?? edgeMidpoint(edge.points)\n const padding = 8\n\n // Measure text (works for both single and multi-line)\n const metrics = measureMultilineText(\n label,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n\n // Wrap in <g class=\"edge-label\"> with reference to the edge it belongs to\n const content = renderMultilineTextWithBackground(\n label,\n mid.x,\n mid.y,\n metrics.width,\n metrics.height,\n fontSizes.edgeLabel,\n padding,\n // Use --_text-sec for better contrast (was --_text-muted)\n f`text-anchor=\"middle\" font-size=\"${fontSizes.edgeLabel}\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-sec)\"`,\n // Increased stroke width from 0.5 to 1 for better label separation from edges\n `rx=\"2\" ry=\"2\" fill=\"var(--bg)\" stroke=\"var(--_inner-stroke)\" stroke-width=\"1\"`,\n )\n\n // Semantic wrapper: links label to its edge via data-from/data-to\n return (\n f`<g class=\"edge-label\" data-from=\"${escapeAttr(edge.source)}\" data-to=\"${escapeAttr(edge.target)}\" data-label=\"${escapeAttr(label)}\">\\n` +\n f` ${content.replace(/\\n/g, '\\n ')}\\n` +\n `</g>`\n )\n}\n\n/**\n * Get the point at `index`, throwing instead of silently producing\n * `undefined` (and downstream `NaN`s). The loops in `edgeMidpoint` only ever\n * pass indices derived from `points.length`, so this should never actually\n * throw — it exists because `noUncheckedIndexedAccess` can't prove that from\n * the loop bounds alone, and a loud failure here is far easier to diagnose\n * than silently corrupted coordinates.\n */\nfunction requirePoint(points: Point[], index: number): Point {\n const point = points[index]\n if (!point) {\n // Unreachable — both call sites in edgeMidpoint only ever pass indices\n // in [0, points.length - 1].\n /* v8 ignore next */\n throw new Error(f`edgeMidpoint: missing point at index ${index}`)\n }\n return point\n}\n\n/** Get the midpoint of a polyline (by walking segments) */\nfunction edgeMidpoint(points: Point[]): Point {\n if (points.length === 0) return { x: 0, y: 0 }\n if (points.length === 1) return points[0]!\n\n // Calculate total length\n let totalLength = 0\n for (let i = 1; i < points.length; i++) {\n totalLength += dist(requirePoint(points, i - 1), requirePoint(points, i))\n }\n\n // Walk to the halfway point\n let remaining = totalLength / 2\n for (let i = 1; i < points.length; i++) {\n const prev = requirePoint(points, i - 1)\n const curr = requirePoint(points, i)\n const segLen = dist(prev, curr)\n if (remaining <= segLen) {\n const t = remaining / segLen\n return {\n x: prev.x + t * (curr.x - prev.x),\n y: prev.y + t * (curr.y - prev.y),\n }\n }\n remaining -= segLen\n }\n\n return points[points.length - 1]!\n}\n\nfunction dist(a: Point, b: Point): number {\n return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2)\n}\n\n// ============================================================================\n// Node rendering\n// ============================================================================\n\n/**\n * Render a complete node: shape + label wrapped in a semantic <g> element.\n *\n * The group includes data attributes for:\n * - data-id: original Mermaid node ID (for edge matching)\n * - data-label: display label text\n * - data-shape: shape type (rectangle, diamond, circle, etc.)\n *\n * @param linksEnabled - Whether a `click`-based `<a href>` link and `<title>`\n * tooltip render (from `options.interactivity !==\n * 'none'`). Default true.\n */\nfunction renderNode(\n node: PositionedNode,\n font: string,\n fontSizes: FontSizes,\n linksEnabled: boolean = true,\n): string {\n const shape = renderNodeShape(node)\n const label = renderNodeLabel(node, font, fontSizes)\n\n // Combine shape and label inside a semantic group\n // This enables reliable node identification without heuristics\n const parts: string[] = []\n const safeClassName = sanitizeClassName(node.className)\n const classAttr = safeClassName ? f`node ${safeClassName}` : 'node'\n\n const interaction = node.interaction\n const groupAttrs = [\n f`class=\"${classAttr}\"`,\n f`data-id=\"${escapeAttr(node.id)}\"`,\n f`data-label=\"${escapeAttr(node.label)}\"`,\n f`data-shape=\"${node.shape}\"`,\n ]\n /*\n * `click A call fn()` is deliberately absent from the markup. This renderer\n * produces a static SVG string and executes nothing a diagram supplies —\n * running diagram-authored script would make every rendered diagram an\n * execution vector. The binding is exposed as data instead, on the\n * `interactions` map `parseMermaid()` returns, and the `data-id` attribute\n * above is the hook a host binds it to. The inert `data-click-callback`\n * attribute once emitted here was removed in #216 — see\n * docs/decisions/no-script-interactivity.md.\n */\n parts.push(f`<g ${groupAttrs.join(' ')}>`)\n\n // An href becomes a real SVG link, which needs no script to work.\n // `interactivity: 'none'` strips it — a link is meaningless in\n // print/rasterized output, the target that level is meant for.\n const href = linksEnabled ? safeHref(interaction?.href) : undefined\n const indent = href ? ' ' : ' '\n if (href) {\n const targetAttr = interaction?.target\n ? f` target=\"${escapeAttr(interaction.target)}\"`\n : ''\n parts.push(f` <a href=\"${escapeAttr(href)}\"${targetAttr}>`)\n }\n\n if (linksEnabled && interaction?.tooltip) {\n // <title> is SVG's native tooltip — no script, no CSS. Gated by\n // linksEnabled alongside href above: both come from the same `click`\n // statement, and 'none' strips both.\n parts.push(f`${indent}<title>${escapeXml(interaction.tooltip)}</title>`)\n }\n\n parts.push(f`${indent}${shape.replace(/\\n/g, f`\\n${indent}`)}`)\n if (label) {\n parts.push(f`${indent}${label.replace(/\\n/g, f`\\n${indent}`)}`)\n }\n\n if (href) parts.push(' </a>')\n parts.push('</g>')\n\n return parts.join('\\n')\n}\n\nfunction renderNodeShape(node: PositionedNode): string {\n const { x, y, width, height, shape, inlineStyle } = node\n\n // Resolve fill and stroke — inline styles (from mermaid `style` directives)\n // override the CSS variable defaults. When no inline style is present, the\n // CSS variable handles theming automatically via color-mix() derivation.\n const fill = escapeAttr(inlineStyle?.fill ?? 'var(--_node-fill)')\n const stroke = escapeAttr(inlineStyle?.stroke ?? 'var(--_node-stroke)')\n const sw = escapeAttr(\n inlineStyle?.['stroke-width'] ?? String(STROKE_WIDTHS.innerBox),\n )\n\n switch (shape) {\n case 'diamond':\n return renderDiamond(x, y, width, height, fill, stroke, sw)\n case 'rounded':\n return renderRoundedRect(x, y, width, height, fill, stroke, sw)\n case 'stadium':\n return renderStadium(x, y, width, height, fill, stroke, sw)\n case 'circle':\n return renderCircle(x, y, width, height, fill, stroke, sw)\n case 'subroutine':\n return renderSubroutine(x, y, width, height, fill, stroke, sw)\n case 'doublecircle':\n return renderDoubleCircle(x, y, width, height, fill, stroke, sw)\n case 'hexagon':\n return renderHexagon(x, y, width, height, fill, stroke, sw)\n case 'cylinder':\n return renderCylinder(x, y, width, height, fill, stroke, sw)\n case 'asymmetric':\n return renderAsymmetric(x, y, width, height, fill, stroke, sw)\n case 'trapezoid':\n return renderTrapezoid(x, y, width, height, fill, stroke, sw)\n case 'trapezoid-alt':\n return renderTrapezoidAlt(x, y, width, height, fill, stroke, sw)\n case 'parallelogram':\n return renderParallelogram(x, y, width, height, fill, stroke, sw)\n case 'parallelogram-alt':\n return renderParallelogramAlt(x, y, width, height, fill, stroke, sw)\n case 'state-start':\n return renderStateStart(x, y, width, height)\n case 'state-end':\n return renderStateEnd(x, y, width, height)\n\n // --- Expanded-syntax shapes (`A@{ shape: ... }`) ---\n case 'document':\n return renderDocument(x, y, width, height, fill, stroke, sw)\n case 'stacked-document':\n return renderStacked(x, y, width, height, fill, stroke, sw, true)\n case 'stacked-process':\n return renderStacked(x, y, width, height, fill, stroke, sw, false)\n case 'card':\n return renderCard(x, y, width, height, fill, stroke, sw)\n case 'lined-process':\n return renderLinedProcess(x, y, width, height, fill, stroke, sw)\n case 'divided-process':\n return renderDividedProcess(x, y, width, height, fill, stroke, sw)\n case 'window-pane':\n return renderWindowPane(x, y, width, height, fill, stroke, sw)\n case 'triangle':\n return renderTriangle(x, y, width, height, fill, stroke, sw, false)\n case 'flipped-triangle':\n return renderTriangle(x, y, width, height, fill, stroke, sw, true)\n case 'filled-circle':\n return renderFilledCircle(x, y, width, height, stroke)\n case 'crossed-circle':\n return renderCrossedCircle(x, y, width, height, fill, stroke, sw)\n case 'fork-join':\n return renderForkJoin(x, y, width, height, stroke)\n case 'notched-pentagon':\n return renderNotchedPentagon(x, y, width, height, fill, stroke, sw)\n case 'sloped-rectangle':\n return renderSlopedRectangle(x, y, width, height, fill, stroke, sw)\n case 'flag':\n return renderFlag(x, y, width, height, fill, stroke, sw)\n case 'bow-tie-rectangle':\n return renderBowTie(x, y, width, height, fill, stroke, sw)\n case 'half-rounded-rectangle':\n return renderHalfRounded(x, y, width, height, fill, stroke, sw)\n case 'brace':\n return renderBraces(x, y, width, height, fill, stroke, sw, 'left')\n case 'brace-right':\n return renderBraces(x, y, width, height, fill, stroke, sw, 'right')\n case 'braces':\n return renderBraces(x, y, width, height, fill, stroke, sw, 'both')\n case 'bolt':\n return renderBolt(x, y, width, height, fill, stroke, sw)\n case 'text':\n case 'anchor':\n // No outline — the label (rendered separately) is the whole node.\n return ''\n\n case 'rectangle':\n default:\n return renderRect(x, y, width, height, fill, stroke, sw)\n }\n}\n\n// --- Basic shapes ---\n\nfunction renderRect(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n return (\n f`<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ` +\n f`rx=\"0\" ry=\"0\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\nfunction renderRoundedRect(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n return (\n f`<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ` +\n f`rx=\"6\" ry=\"6\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\nfunction renderStadium(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const r = h / 2\n return (\n f`<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ` +\n f`rx=\"${r}\" ry=\"${r}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\nfunction renderCircle(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const r = Math.min(w, h) / 2\n return (\n f`<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${r}\" ` +\n f`fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\nfunction renderDiamond(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const hw = w / 2\n const hh = h / 2\n const points = [\n f`${cx},${cy - hh}`, // top\n f`${cx + hw},${cy}`, // right\n f`${cx},${cy + hh}`, // bottom\n f`${cx - hw},${cy}`, // left\n ].join(' ')\n\n return f`<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n// --- Batch 1 shapes ---\n\n/** Subroutine: rectangle with double vertical borders on left and right */\nfunction renderSubroutine(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = 8 // distance from edge to inner vertical line\n return (\n f`<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ` +\n f`rx=\"0\" ry=\"0\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n f`\\n<line x1=\"${x + inset}\" y1=\"${y}\" x2=\"${x + inset}\" y2=\"${y + h}\" ` +\n f`stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n f`\\n<line x1=\"${x + w - inset}\" y1=\"${y}\" x2=\"${x + w - inset}\" y2=\"${y + h}\" ` +\n f`stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Double circle: two concentric circles with a gap between them */\nfunction renderDoubleCircle(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const outerR = Math.min(w, h) / 2\n const innerR = outerR - 5 // 5px gap between rings\n return (\n f`<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${outerR}\" ` +\n f`fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n f`\\n<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${innerR}\" ` +\n f`fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Hexagon: 6-point polygon with flat top/bottom and angled sides */\nfunction renderHexagon(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = h / 4 // horizontal inset for the angled sides\n const points = [\n f`${x + inset},${y}`, // top-left\n f`${x + w - inset},${y}`, // top-right\n f`${x + w},${y + h / 2}`, // mid-right\n f`${x + w - inset},${y + h}`, // bottom-right\n f`${x + inset},${y + h}`, // bottom-left\n f`${x},${y + h / 2}`, // mid-left\n ].join(' ')\n\n return f`<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n// --- Batch 2 shapes ---\n\n/** Cylinder / database: top ellipse cap + body rect + bottom ellipse */\nfunction renderCylinder(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const ry = 7 // ellipse vertical radius for the cap\n const cx = x + w / 2\n const bodyTop = y + ry\n const bodyH = h - 2 * ry\n\n return (\n // Body rectangle (no top border — covered by top ellipse)\n f`<rect x=\"${x}\" y=\"${bodyTop}\" width=\"${w}\" height=\"${bodyH}\" ` +\n f`fill=\"${fill}\" stroke=\"none\" />` +\n // Left and right body borders\n f`\\n<line x1=\"${x}\" y1=\"${bodyTop}\" x2=\"${x}\" y2=\"${bodyTop + bodyH}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n f`\\n<line x1=\"${x + w}\" y1=\"${bodyTop}\" x2=\"${x + w}\" y2=\"${bodyTop + bodyH}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n // Bottom ellipse (half visible)\n f`\\n<ellipse cx=\"${cx}\" cy=\"${y + h - ry}\" rx=\"${w / 2}\" ry=\"${ry}\" ` +\n f`fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />` +\n // Top ellipse (full, on top)\n f`\\n<ellipse cx=\"${cx}\" cy=\"${bodyTop}\" rx=\"${w / 2}\" ry=\"${ry}\" ` +\n f`fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Asymmetric / flag: rectangle with a pointed left edge */\nfunction renderAsymmetric(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const indent = 12 // how far the point indents\n const points = [\n f`${x + indent},${y}`, // top-left (indented)\n f`${x + w},${y}`, // top-right\n f`${x + w},${y + h}`, // bottom-right\n f`${x + indent},${y + h}`, // bottom-left (indented)\n f`${x},${y + h / 2}`, // left point\n ].join(' ')\n\n return f`<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n/** Trapezoid [/text\\]: wider bottom, narrower top */\nfunction renderTrapezoid(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = w * 0.15 // top edge is narrower by this amount on each side\n const points = [\n f`${x + inset},${y}`, // top-left (indented)\n f`${x + w - inset},${y}`, // top-right (indented)\n f`${x + w},${y + h}`, // bottom-right (full width)\n f`${x},${y + h}`, // bottom-left (full width)\n ].join(' ')\n\n return f`<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n/** Trapezoid-alt [\\text/]: wider top, narrower bottom */\nfunction renderTrapezoidAlt(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = w * 0.15 // bottom edge is narrower\n const points = [\n f`${x},${y}`, // top-left (full width)\n f`${x + w},${y}`, // top-right (full width)\n f`${x + w - inset},${y + h}`, // bottom-right (indented)\n f`${x + inset},${y + h}`, // bottom-left (indented)\n ].join(' ')\n\n return f`<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n/**\n * Parallelogram [/text/]: leans right.\n *\n * Unlike the trapezoids, both sloped sides run the same direction — the top\n * edge shifts right by `inset` and the bottom edge shifts left by the same\n * amount, so opposite sides stay parallel.\n */\nfunction renderParallelogram(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = w * 0.15\n const points = [\n f`${x + inset},${y}`, // top-left (shifted right)\n f`${x + w},${y}`, // top-right\n f`${x + w - inset},${y + h}`, // bottom-right (shifted left)\n f`${x},${y + h}`, // bottom-left\n ].join(' ')\n\n return f`<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n/** Parallelogram-alt [\\text\\]: leans left — the mirror of renderParallelogram. */\nfunction renderParallelogramAlt(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = w * 0.15\n const points = [\n f`${x},${y}`, // top-left\n f`${x + w - inset},${y}`, // top-right (shifted left)\n f`${x + w},${y + h}`, // bottom-right\n f`${x + inset},${y + h}`, // bottom-left (shifted right)\n ].join(' ')\n\n return f`<polygon points=\"${points}\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n}\n\n// --- Expanded-syntax shapes (`A@{ shape: ... }`) ---\n//\n// Each of these is reachable only through the expanded metadata syntax; the\n// classic bracket forms have no spelling for them. See\n// packages/mermaid-parser/src/expanded-shapes.ts for the semantic-name →\n// geometry alias table.\n\n/** Shared attribute string for a filled, stroked path. */\nfunction shapeAttrs(fill: string, stroke: string, sw: string): string {\n return f`fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${sw}\"`\n}\n\n/**\n * Document: rectangle with a wavy bottom edge.\n *\n * The wave is two cubic segments — down then up — so the edge returns to its\n * starting height at the right corner and the shape tiles cleanly when\n * stacked (see renderStacked).\n */\nfunction renderDocument(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const waveH = Math.min(10, h * 0.16)\n const base = y + h - waveH\n // Both ends land on `base`, so the left and right sides are the same\n // height. Ending the curve anywhere else makes every document node\n // visibly lopsided.\n const d =\n f`M ${x} ${y} L ${x + w} ${y} L ${x + w} ${base} ` +\n f`C ${x + w * 0.75} ${base + waveH * 1.8} ${x + w * 0.25} ${base - waveH * 0.8} ${x} ${base} Z`\n return f`<path d=\"${d}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Stacked document / process: offset copies behind the front shape. */\nfunction renderStacked(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n isDocument: boolean,\n): string {\n const offset = 5\n const frontW = w - offset * 2\n const frontH = h - offset * 2\n const parts: string[] = []\n\n // Two offset copies behind, back to front.\n for (let i = 2; i >= 1; i--) {\n parts.push(\n f`<rect x=\"${x + offset * i}\" y=\"${y + offset * (2 - i)}\" width=\"${frontW}\" height=\"${frontH}\" ` +\n f`${shapeAttrs(fill, stroke, sw)} />`,\n )\n }\n\n parts.push(\n isDocument\n ? renderDocument(x, y + offset * 2, frontW, frontH, fill, stroke, sw)\n : f`<rect x=\"${x}\" y=\"${y + offset * 2}\" width=\"${frontW}\" height=\"${frontH}\" ${shapeAttrs(fill, stroke, sw)} />`,\n )\n\n return parts.join('\\n')\n}\n\n/** Card / notched rectangle: top-left corner clipped. */\nfunction renderCard(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const notch = Math.min(14, w * 0.12, h * 0.3)\n const points = [\n f`${x + notch},${y}`,\n f`${x + w},${y}`,\n f`${x + w},${y + h}`,\n f`${x},${y + h}`,\n f`${x},${y + notch}`,\n ].join(' ')\n return f`<polygon points=\"${points}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Lined process: rectangle with a vertical rule inset from the left edge. */\nfunction renderLinedProcess(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const inset = Math.min(12, w * 0.12)\n return (\n f`<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ${shapeAttrs(fill, stroke, sw)} />\\n` +\n f`<line x1=\"${x + inset}\" y1=\"${y}\" x2=\"${x + inset}\" y2=\"${y + h}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Divided process: rectangle split by a horizontal rule near the top. */\nfunction renderDividedProcess(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const split = y + Math.min(16, h * 0.3)\n return (\n f`<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ${shapeAttrs(fill, stroke, sw)} />\\n` +\n f`<line x1=\"${x}\" y1=\"${split}\" x2=\"${x + w}\" y2=\"${split}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Window pane / internal storage: rectangle quartered by a cross. */\nfunction renderWindowPane(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const vx = x + Math.min(16, w * 0.16)\n const hy = y + Math.min(14, h * 0.28)\n return (\n f`<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" ${shapeAttrs(fill, stroke, sw)} />\\n` +\n f`<line x1=\"${vx}\" y1=\"${y}\" x2=\"${vx}\" y2=\"${y + h}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />\\n` +\n f`<line x1=\"${x}\" y1=\"${hy}\" x2=\"${x + w}\" y2=\"${hy}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Triangle, apex up (extract) or apex down (manual file). */\nfunction renderTriangle(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n flipped: boolean,\n): string {\n const points = flipped\n ? [f`${x},${y}`, f`${x + w},${y}`, f`${x + w / 2},${y + h}`]\n : [f`${x + w / 2},${y}`, f`${x + w},${y + h}`, f`${x},${y + h}`]\n return f`<polygon points=\"${points.join(' ')}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Filled circle (junction): a solid dot, so it takes the stroke color as fill. */\nfunction renderFilledCircle(\n x: number,\n y: number,\n w: number,\n h: number,\n stroke: string,\n): string {\n const r = Math.min(w, h) / 2 - 2\n return f`<circle cx=\"${x + w / 2}\" cy=\"${y + h / 2}\" r=\"${r}\" fill=\"${stroke}\" stroke=\"${stroke}\" />`\n}\n\n/** Crossed circle (summary): circle with an X through it. */\nfunction renderCrossedCircle(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const r = Math.min(w, h) / 2 - 2\n // Cross arms meet the circumference at 45°, so offset by r/√2.\n const d = r / Math.SQRT2\n return (\n f`<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${r}\" ${shapeAttrs(fill, stroke, sw)} />\\n` +\n f`<line x1=\"${cx - d}\" y1=\"${cy - d}\" x2=\"${cx + d}\" y2=\"${cy + d}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />\\n` +\n f`<line x1=\"${cx + d}\" y1=\"${cy - d}\" x2=\"${cx - d}\" y2=\"${cy + d}\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`\n )\n}\n\n/** Fork/join: a solid bar, drawn in the stroke color. */\nfunction renderForkJoin(\n x: number,\n y: number,\n w: number,\n h: number,\n stroke: string,\n): string {\n const barH = Math.min(8, h)\n return f`<rect x=\"${x}\" y=\"${y + (h - barH) / 2}\" width=\"${w}\" height=\"${barH}\" fill=\"${stroke}\" stroke=\"${stroke}\" />`\n}\n\n/** Notched pentagon (loop limit): both top corners clipped. */\nfunction renderNotchedPentagon(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const notch = Math.min(14, w * 0.12, h * 0.3)\n const points = [\n f`${x + notch},${y}`,\n f`${x + w - notch},${y}`,\n f`${x + w},${y + notch}`,\n f`${x + w},${y + h}`,\n f`${x},${y + h}`,\n f`${x},${y + notch}`,\n ].join(' ')\n return f`<polygon points=\"${points}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Sloped rectangle (manual input): top edge slopes up to the right. */\nfunction renderSlopedRectangle(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const slope = Math.min(12, h * 0.3)\n const points = [\n f`${x},${y + slope}`,\n f`${x + w},${y}`,\n f`${x + w},${y + h}`,\n f`${x},${y + h}`,\n ].join(' ')\n return f`<polygon points=\"${points}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Flag / paper tape: wavy top and bottom edges. */\nfunction renderFlag(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const waveH = Math.min(8, h * 0.14)\n const d =\n f`M ${x} ${y + waveH} ` +\n f`C ${x + w * 0.25} ${y - waveH} ${x + w * 0.75} ${y + waveH * 2} ${x + w} ${y + waveH} ` +\n f`L ${x + w} ${y + h - waveH} ` +\n f`C ${x + w * 0.75} ${y + h + waveH} ${x + w * 0.25} ${y + h - waveH * 2} ${x} ${y + h - waveH} Z`\n return f`<path d=\"${d}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Bow-tie rectangle (stored data): left and right edges curve inward. */\nfunction renderBowTie(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const bow = Math.min(14, w * 0.12)\n const d =\n f`M ${x + bow} ${y} L ${x + w} ${y} ` +\n f`Q ${x + w - bow * 1.4} ${y + h / 2} ${x + w} ${y + h} ` +\n f`L ${x + bow} ${y + h} ` +\n f`Q ${x + bow * 1.4} ${y + h / 2} ${x + bow} ${y} Z`\n return f`<path d=\"${d}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/** Delay / half-rounded rectangle: the right end is a semicircle. */\nfunction renderHalfRounded(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const r = h / 2\n const straight = Math.max(0, w - r)\n const d =\n f`M ${x} ${y} L ${x + straight} ${y} ` +\n f`A ${r} ${r} 0 0 1 ${x + straight} ${y + h} ` +\n f`L ${x} ${y + h} Z`\n return f`<path d=\"${d}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n/**\n * Brace shapes: a rectangle body flanked by curly braces.\n *\n * Mermaid's `brace`/`comment` is a left brace, `brace-r` a right brace, and\n * `braces` both. The body itself is unfilled so the braces read as annotation\n * marks rather than as a container.\n */\nfunction renderBraces(\n x: number,\n y: number,\n w: number,\n h: number,\n _fill: string,\n stroke: string,\n sw: string,\n side: 'left' | 'right' | 'both',\n): string {\n const armW = Math.min(10, w * 0.12)\n // Unfilled: a filled body reads as a container, which is the opposite of\n // what a brace annotation means. The rect stays only to reserve the\n // label area; `_fill` is deliberately unused.\n const parts = [\n f`<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" fill=\"none\" stroke=\"none\" />`,\n ]\n\n const leftBrace =\n f`M ${x + armW} ${y} Q ${x} ${y} ${x} ${y + h * 0.25} ` +\n f`Q ${x} ${y + h / 2} ${x - armW * 0.4} ${y + h / 2} ` +\n f`Q ${x} ${y + h / 2} ${x} ${y + h * 0.75} ` +\n f`Q ${x} ${y + h} ${x + armW} ${y + h}`\n const rightBrace =\n f`M ${x + w - armW} ${y} Q ${x + w} ${y} ${x + w} ${y + h * 0.25} ` +\n f`Q ${x + w} ${y + h / 2} ${x + w + armW * 0.4} ${y + h / 2} ` +\n f`Q ${x + w} ${y + h / 2} ${x + w} ${y + h * 0.75} ` +\n f`Q ${x + w} ${y + h} ${x + w - armW} ${y + h}`\n\n if (side === 'left' || side === 'both') {\n parts.push(\n f`<path d=\"${leftBrace}\" fill=\"none\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`,\n )\n }\n if (side === 'right' || side === 'both') {\n parts.push(\n f`<path d=\"${rightBrace}\" fill=\"none\" stroke=\"${stroke}\" stroke-width=\"${sw}\" />`,\n )\n }\n\n return parts.join('\\n')\n}\n\n/** Lightning bolt (communication link). */\nfunction renderBolt(\n x: number,\n y: number,\n w: number,\n h: number,\n fill: string,\n stroke: string,\n sw: string,\n): string {\n const points = [\n f`${x + w * 0.42},${y}`,\n f`${x + w},${y}`,\n f`${x + w * 0.62},${y + h * 0.42}`,\n f`${x + w},${y + h * 0.42}`,\n f`${x + w * 0.3},${y + h}`,\n f`${x + w * 0.5},${y + h * 0.55}`,\n f`${x},${y + h * 0.55}`,\n ].join(' ')\n return f`<polygon points=\"${points}\" ${shapeAttrs(fill, stroke, sw)} />`\n}\n\n// --- Batch 3: State diagram pseudostates ---\n\n/** State start: small filled circle using primary text color */\nfunction renderStateStart(x: number, y: number, w: number, h: number): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const r = Math.min(w, h) / 2 - 2\n return f`<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${r}\" fill=\"var(--_text)\" stroke=\"none\" />`\n}\n\n/** State end: bullseye — outer ring + inner filled circle using primary text color */\nfunction renderStateEnd(x: number, y: number, w: number, h: number): string {\n const cx = x + w / 2\n const cy = y + h / 2\n const outerR = Math.min(w, h) / 2 - 2\n const innerR = outerR - 4\n return (\n f`<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${outerR}\" ` +\n f`fill=\"none\" stroke=\"var(--_text)\" stroke-width=\"${STROKE_WIDTHS.innerBox * 2}\" />` +\n f`\\n<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${innerR}\" fill=\"var(--_text)\" stroke=\"none\" />`\n )\n}\n\n// ============================================================================\n// Node label rendering\n// ============================================================================\n\n// `_font` isn't read here but is kept to match the `(entity, font)` signature\n// threaded through the rest of the render* functions in this file.\nfunction renderNodeLabel(\n node: PositionedNode,\n _font: string,\n fontSizes: FontSizes,\n): string {\n // State pseudostates have no label\n if (node.shape === 'state-start' || node.shape === 'state-end') {\n if (!node.label) return ''\n }\n\n const cx = node.x + node.width / 2\n const cy = node.y + node.height / 2\n\n // Resolve text color — inline styles can override the CSS variable default.\n // When there's no explicit `color` but there IS a concrete, resolvable\n // `fill` (e.g. from classDef/style), compute a readable black/white text\n // color from the fill's luminance instead of defaulting to the ambient\n // theme foreground, which can be unreadable against a custom fill (e.g.\n // white theme text on a light pastel fill in dark mode). See issue #55.\n const textColor = escapeAttr(\n node.inlineStyle?.color ??\n getReadableTextColor(node.inlineStyle?.fill, 'var(--_text)'),\n )\n\n let attrs = f`text-anchor=\"middle\" font-size=\"${fontSizes.nodeLabel}\" font-weight=\"${FONT_WEIGHTS.nodeLabel}\" fill=\"${textColor}\"`\n\n // Per-node font-family override (from `style A font-family:...` or\n // classDef/class). Emitted as an inline `style` attribute rather than a\n // `font-family` presentation attribute: the global font is applied via a\n // `text { font-family: ... }` rule in the embedded <style> block (see\n // theme.ts buildStyleBlock), and a presentation attribute always loses to\n // any stylesheet rule regardless of selector specificity. An inline `style`\n // attribute has the highest priority in the cascade, so it reliably\n // overrides the global rule for just this node while every other node\n // keeps falling back to the global font stack. See issue #57.\n const fontFamily = node.inlineStyle?.['font-family']\n if (fontFamily) {\n attrs += f` style=\"font-family: ${escapeAttr(fontFamily)};\"`\n }\n\n return renderMultilineText(node.label, cx, cy, fontSizes.nodeLabel, attrs)\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Escape a string for embedding as a *multiline-safe* XML attribute value —\n * `escapeAttr()` plus tab/LF/CR as numeric character references.\n *\n * A strict XML parser applies attribute-value normalization on the way in:\n * any literal tab, LF, or CR in the value is collapsed to a single space.\n * (This is why a standalone .svg file opened directly, or any output run\n * through `DOMParser` with an XML/`image/svg+xml` mimetype, would silently\n * flatten a multiline `data-src` back to one line.) Character references\n * are exempt from that normalization — `&#10;` round-trips as an actual\n * newline — so diagram source, which is virtually always multiline, needs\n * this rather than `escapeAttr()` alone to survive the trip intact.\n */\nfunction escapeMultilineAttr(value: string): string {\n return escapeAttr(value)\n .replace(/\\r/g, '&#13;')\n .replace(/\\n/g, '&#10;')\n .replace(/\\t/g, '&#9;')\n}\n\n/**\n * Splice a `data-src` attribute (the original diagram source, escaped) onto\n * an already-built root `<svg ...>` opening tag, e.g. from\n * `embedSource: true`. Applied to the string `svgOpenTag()` returns rather\n * than the diagram source's own markup, so it always lands on the root\n * element regardless of diagram type. No-op when `source` is undefined.\n */\nexport function withDataSrc(\n svgTag: string,\n source: string | undefined,\n): string {\n if (source === undefined) return svgTag\n return svgTag.replace(\n '<svg ',\n f`<svg data-src=\"${escapeMultilineAttr(source)}\" `,\n )\n}\n","// ============================================================================\n// `resolveColors` — substitute concrete sRGB values for the CSS `var(--…)`\n// and `color-mix(…)` expressions the SVG renderer emits.\n//\n// Browsers evaluate both natively, which is what makes the emitted SVG a\n// live function of its CSS custom properties (see packages/core/src/theme.ts and\n// docs/theming.md). Every other consumer — resvg, librsvg, Inkscape,\n// ImageMagick, anything headless-Chrome-free — implements neither, and\n// rasterizes the whole theme to black (GitHub issue #456). This pass runs\n// over the finished SVG string and evaluates each expression the way a\n// browser would, against the same `<style>` declarations the renderer just\n// wrote, so the MIX percentages in packages/core/src/theme.ts stay the single source of\n// truth: nothing here re-states a number.\n//\n// Scope, deliberately narrow — this is not a CSS engine:\n// - Custom properties are read from the root `<svg style=\"--bg:…\">` and\n// from `svg { --_line: … }` declarations in `<style>` blocks. The\n// renderer only ever declares them on the root, so one flat scope is\n// exact; no cascade/specificity is modelled.\n// - `color-mix()` is evaluated only `in srgb` (the only space the\n// renderer emits) and only over colors `parseCssColor` understands\n// (hex, `rgb()`/`rgba()`, `transparent`). Anything else is left as-is.\n// - `var(--x)` with no declaration and no fallback is left untouched —\n// e.g. `font: 'var(--font-family-body)'`, a host-page variable this\n// library cannot know the value of.\n// - Element text content (labels, `<title>`) and the `data-src` source\n// stamp are never touched: only attribute values and `<style>` blocks\n// are CSS, so only those are rewritten.\n// ============================================================================\n\nimport type { DiagramColors } from '@zombie-mermaid/core'\nimport { formatCssColor, mixSrgb, parseCssColor } from '@zombie-mermaid/core'\n\n// ============================================================================\n// Expression evaluation\n// ============================================================================\n\n/**\n * Custom-property lookup with memoization and a cycle guard. Values are\n * stored raw (as declared) and evaluated on first use, so a declaration\n * can reference another declared later in the block.\n */\nclass VarScope {\n private readonly raw = new Map<string, string>()\n private readonly resolved = new Map<string, string | undefined>()\n private readonly inProgress = new Set<string>()\n\n declare(name: string, value: string): void {\n this.raw.set(name, value)\n this.resolved.delete(name)\n }\n\n /** Evaluated value of `name`, or undefined when unknown or cyclic. */\n lookup(name: string): string | undefined {\n if (this.resolved.has(name)) return this.resolved.get(name)\n const rawValue = this.raw.get(name)\n if (rawValue === undefined || this.inProgress.has(name)) return undefined\n this.inProgress.add(name)\n const value = evaluateExpression(rawValue, this).trim()\n this.inProgress.delete(name)\n // A value that still contains an unresolvable reference is treated as\n // unknown, so a `var()` pointing at it keeps its own fallback instead of\n // being replaced by something a rasterizer can't parse either.\n const usable = !/\\bvar\\(|\\bcolor-mix\\(/.test(value)\n this.resolved.set(name, usable ? value : undefined)\n return usable ? value : undefined\n }\n}\n\n/** Index of the `)` matching the `(` at `open`, or -1 if unbalanced. */\nfunction matchingParen(text: string, open: number): number {\n let depth = 0\n for (let i = open; i < text.length; i++) {\n const ch = text[i]\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth === 0) return i\n }\n }\n return -1\n}\n\n/** Split on top-level commas only (commas nested in parens stay put). */\nfunction splitTopLevel(text: string): string[] {\n const parts: string[] = []\n let depth = 0\n let start = 0\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]\n if (ch === '(') depth++\n else if (ch === ')') depth--\n else if (ch === ',' && depth === 0) {\n parts.push(text.slice(start, i))\n start = i + 1\n }\n }\n parts.push(text.slice(start))\n return parts\n}\n\n/**\n * Matches the next `var(` or `color-mix(` not glued to a preceding\n * identifier character. Instantiated per `evaluateExpression` call (it's\n * recursive, and a shared global regex's `lastIndex` would be clobbered by\n * the inner call).\n */\nconst FUNCTION_START_SOURCE = /(?<![\\w-])(var|color-mix)\\(/g.source\n\n/**\n * Evaluate `var(name[, fallback])`. Returns the replacement text, or null\n * when the reference can't be resolved and should be left verbatim.\n */\nfunction evaluateVar(args: string, scope: VarScope): string | null {\n const [nameRaw, ...fallbackParts] = splitTopLevel(args)\n const name = (nameRaw ?? '').trim()\n const value = scope.lookup(name)\n if (value !== undefined) return value\n if (fallbackParts.length > 0) return fallbackParts.join(',').trim()\n return null\n}\n\n/** Parse one `color-mix()` operand: `<color> [<percentage>]`, in either order. */\nfunction parseMixOperand(\n operand: string,\n): { color: string; pct: number | undefined } | null {\n const tokens = operand.trim().split(/\\s+/)\n let color: string | undefined\n let pct: number | undefined\n for (const token of tokens) {\n if (/^[\\d.]+%$/.test(token)) {\n if (pct !== undefined) return null\n pct = Number(token.slice(0, -1))\n } else {\n if (color !== undefined) return null\n color = token\n }\n }\n if (color === undefined) return null\n return { color, pct }\n}\n\n/**\n * Evaluate `color-mix(in srgb, c1 [p1%], c2 [p2%])`. Returns the\n * replacement text, or null when the expression isn't one this module\n * can evaluate (a non-srgb space, an operand that isn't a concrete color).\n */\nfunction evaluateColorMix(args: string): string | null {\n const parts = splitTopLevel(args)\n if (parts.length !== 3) return null\n const [space, op1, op2] = parts\n if (\n space === undefined ||\n op1 === undefined ||\n op2 === undefined ||\n !/^\\s*in\\s+srgb\\s*$/i.test(space)\n ) {\n return null\n }\n const a = parseMixOperand(op1)\n const b = parseMixOperand(op2)\n if (!a || !b) return null\n const c1 = parseCssColor(a.color)\n const c2 = parseCssColor(b.color)\n if (!c1 || !c2) return null\n const mixed = mixSrgb(c1, c2, a.pct, b.pct)\n return mixed ? formatCssColor(mixed) : null\n}\n\n/**\n * Replace every evaluable `var()` / `color-mix()` in `text`, innermost\n * first. Expressions that can't be evaluated are kept verbatim (with their\n * arguments still evaluated as far as possible), and scanning resumes after\n * them, so an unknown variable can never stall the pass.\n */\nfunction evaluateExpression(text: string, scope: VarScope): string {\n let out = ''\n let cursor = 0\n const functionStart = new RegExp(FUNCTION_START_SOURCE, 'g')\n let match: RegExpExecArray | null\n while ((match = functionStart.exec(text)) !== null) {\n const fn = match[1]\n if (fn === undefined) continue\n const start = match.index\n const open = start + fn.length\n const close = matchingParen(text, open)\n if (close === -1) break\n const args = evaluateExpression(text.slice(open + 1, close), scope)\n const replacement =\n fn === 'var' ? evaluateVar(args, scope) : evaluateColorMix(args)\n out += text.slice(cursor, start)\n out += replacement ?? `${fn}(${args})`\n cursor = close + 1\n functionStart.lastIndex = cursor\n }\n return out + text.slice(cursor)\n}\n\n/**\n * Evaluate one CSS value against a flat set of custom-property\n * declarations — the expression engine behind `resolveCssColors`, exposed\n * so it can be unit-tested (and reused) without building an SVG around it.\n *\n * @example\n * evaluateCssColorValue('color-mix(in srgb, var(--fg) 50%, var(--bg))',\n * { '--fg': '#000000', '--bg': '#ffffff' }) // → '#808080'\n */\nexport function evaluateCssColorValue(\n value: string,\n declarations: Record<string, string>,\n): string {\n const scope = new VarScope()\n for (const [name, raw] of Object.entries(declarations)) {\n scope.declare(name, raw)\n }\n return evaluateExpression(value, scope)\n}\n\n// ============================================================================\n// SVG document pass\n// ============================================================================\n\n/** `--name: value` custom-property declarations inside a CSS rule body or a `style` attribute. */\nconst CUSTOM_PROPERTY_RE = /(--[\\w-]+)\\s*:\\s*([^;{}]+)/g\n\n/** `name=\"value\"` / `name='value'` attribute pairs inside a start tag. */\nconst ATTRIBUTE_RE = /(\\s)([\\w:.-]+)(=)(\"[^\"]*\"|'[^']*')/g\n\n/**\n * Attributes whose values are never CSS: the `embedSource` stamp holds the\n * diagram's own text, which may legitimately contain `var(` in a label.\n */\nconst SKIP_ATTRIBUTES = new Set(['data-src'])\n\nfunction collectDeclarations(css: string, scope: VarScope): void {\n CUSTOM_PROPERTY_RE.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = CUSTOM_PROPERTY_RE.exec(css)) !== null) {\n const [, name, value] = match\n if (name !== undefined && value !== undefined) {\n scope.declare(name, value.trim())\n }\n }\n}\n\n/** Rewrite the attribute values of one start tag (the text between `<` and `>`). */\nfunction resolveTagAttributes(tag: string, scope: VarScope): string {\n return tag.replace(\n ATTRIBUTE_RE,\n (whole, space: string, name: string, eq: string, quoted: string) => {\n if (SKIP_ATTRIBUTES.has(name)) return whole\n const quote = quoted[0] ?? '\"'\n const value = quoted.slice(1, -1)\n return `${space}${name}${eq}${quote}${evaluateExpression(value, scope)}${quote}`\n },\n )\n}\n\n/**\n * Replace every `var(--…)` and `color-mix(…)` in an SVG produced by this\n * library's renderer with its computed sRGB value (`#rrggbb`, or\n * `rgba(r, g, b, a)` when translucent).\n *\n * `colors` seeds the variable scope with the caller's palette; the SVG's\n * own declarations (the root `style` attribute and any `<style>` block)\n * are then read on top, so the derived `--_*` variables resolve through\n * exactly the `color-mix()` rules `buildStyleBlock` emitted. See the\n * module comment for what is and isn't rewritten.\n */\nexport function resolveCssColors(svg: string, colors: DiagramColors): string {\n const scope = new VarScope()\n scope.declare('--bg', colors.bg)\n scope.declare('--fg', colors.fg)\n if (colors.line) scope.declare('--line', colors.line)\n if (colors.accent) scope.declare('--accent', colors.accent)\n if (colors.muted) scope.declare('--muted', colors.muted)\n if (colors.surface) scope.declare('--surface', colors.surface)\n if (colors.border) scope.declare('--border', colors.border)\n\n // Pass 1: gather every custom-property declaration in the document —\n // `<style>` rule bodies and `style=\"…\"` attributes — before rewriting\n // anything, so a declaration can be referenced from earlier in the text.\n for (const block of svg.matchAll(/<style[^>]*>([\\s\\S]*?)<\\/style>/g)) {\n const css = block[1]\n if (css !== undefined) collectDeclarations(css, scope)\n }\n for (const attr of svg.matchAll(/\\sstyle=(\"[^\"]*\"|'[^']*')/g)) {\n const quoted = attr[1]\n if (quoted !== undefined) {\n collectDeclarations(quoted.slice(1, -1), scope)\n }\n }\n\n // Pass 2: rewrite. Walk the markup, evaluating `<style>` contents and\n // start-tag attribute values; leave text nodes untouched.\n let out = ''\n let cursor = 0\n while (cursor < svg.length) {\n const lt = svg.indexOf('<', cursor)\n if (lt === -1) break\n out += svg.slice(cursor, lt)\n\n if (svg.startsWith('<style', lt)) {\n const openEnd = svg.indexOf('>', lt)\n const closeStart = svg.indexOf('</style>', lt)\n if (openEnd === -1 || closeStart === -1) break\n out += svg.slice(lt, openEnd + 1)\n out += evaluateExpression(svg.slice(openEnd + 1, closeStart), scope)\n out += '</style>'\n cursor = closeStart + '</style>'.length\n continue\n }\n\n // Find the end of this tag, skipping `>` characters inside quoted\n // attribute values (escapeXml encodes them, but be safe for any raw\n // attribute the renderer passes through).\n let gt = -1\n let quote: string | null = null\n for (let i = lt + 1; i < svg.length; i++) {\n const ch = svg[i]\n if (quote !== null) {\n if (ch === quote) quote = null\n } else if (ch === '\"' || ch === \"'\") {\n quote = ch\n } else if (ch === '>') {\n gt = i\n break\n }\n }\n if (gt === -1) break\n const tag = svg.slice(lt, gt + 1)\n out +=\n tag.startsWith('<!') || tag.startsWith('<?') || tag.startsWith('</')\n ? tag\n : resolveTagAttributes(tag, scope)\n cursor = gt + 1\n }\n return out + svg.slice(cursor)\n}\n","/**\n * Class diagram layout engine (ELK.js).\n *\n * Each class box has 3 compartments:\n * 1. Header (class name + optional annotation)\n * 2. Attributes section\n * 3. Methods section\n */\n\nimport type { ElkNode, ElkExtendedEdge } from 'elkjs'\nimport type {\n ClassDiagram,\n ClassNode,\n ClassMember,\n PositionedClassDiagram,\n PositionedClassNode,\n PositionedClassNote,\n PositionedClassRelationship,\n} from '@zombie-mermaid/mermaid-parser'\nimport { formatClassMember } from '@zombie-mermaid/mermaid-parser'\nimport type { ClassRenderOptions } from '@zombie-mermaid/core'\nimport {\n estimateTextWidth,\n estimateMonoTextWidth,\n FONT_WEIGHTS,\n resolveFontSizes,\n} from '../styles.ts'\nimport { elkLayoutSync } from '../elk-instance.ts'\nimport {\n extractEdgePoints,\n extractEdgeLabelPosition,\n} from '../layout-engine/elk-adapter-utils.ts'\nimport {\n ELK_DIRECTION_FALLBACK,\n baseElkLayoutOptions,\n buildElkEdge,\n buildElkLeafNode,\n directionToElk,\n} from '../layout-engine/elk-graph-builder.ts'\nimport { measureMultilineText, resolveNodeStyle } from '@zombie-mermaid/core'\n\n/** Layout constants for class diagrams */\nexport const CLS = {\n padding: 40,\n boxPadX: 8,\n headerBaseHeight: 32,\n annotationHeight: 16,\n memberRowHeight: 20,\n sectionPadY: 8,\n emptySectionHeight: 8,\n minWidth: 120,\n memberFontSize: 11,\n memberFontWeight: 400,\n nodeSpacing: 40,\n layerSpacing: 60,\n /** Horizontal / vertical padding inside a note box, around its text */\n notePadX: 10,\n notePadY: 6,\n} as const\n\n/**\n * Layout id for the i-th note. A class id is a run of non-whitespace\n * (`\\S+` in the parser), so an id containing a space can never collide\n * with one; ELK treats ids as opaque strings.\n */\nexport function classNoteId(index: number): string {\n return `note ${index}`\n}\n\n/** Layout id for the dotted link joining note `noteId` to its class. */\nfunction classNoteLinkId(noteId: string): string {\n return `${noteId} link`\n}\n\ntype ClassSizeMap = Map<\n string,\n {\n width: number\n height: number\n headerHeight: number\n attrHeight: number\n methodHeight: number\n }\n>\n\n/** Size of each note box, keyed by its layout id. */\ntype NoteSizeMap = Map<string, { width: number; height: number }>\n\n/** Build ELK graph and size map from a class diagram. */\nfunction buildClassElkGraph(\n diagram: ClassDiagram,\n options: ClassRenderOptions,\n): { elkGraph: ElkNode; classSizes: ClassSizeMap; noteSizes: NoteSizeMap } {\n const classSizes: ClassSizeMap = new Map()\n const noteSizes: NoteSizeMap = new Map()\n const fontSizes = resolveFontSizes(options.fontSizes)\n\n for (const cls of diagram.classes) {\n const headerHeight = cls.annotation\n ? CLS.headerBaseHeight + CLS.annotationHeight\n : CLS.headerBaseHeight\n\n const attrHeight =\n cls.attributes.length > 0\n ? cls.attributes.length * CLS.memberRowHeight + CLS.sectionPadY\n : CLS.emptySectionHeight\n\n const methodHeight =\n cls.methods.length > 0\n ? cls.methods.length * CLS.memberRowHeight + CLS.sectionPadY\n : CLS.emptySectionHeight\n\n const headerTextW = estimateTextWidth(\n cls.label,\n fontSizes.nodeLabel,\n FONT_WEIGHTS.nodeLabel,\n )\n const maxAttrW = maxMemberWidth(cls.attributes)\n const maxMethodW = maxMemberWidth(cls.methods)\n const width = Math.max(\n CLS.minWidth,\n headerTextW + CLS.boxPadX * 2,\n maxAttrW + CLS.boxPadX * 2,\n maxMethodW + CLS.boxPadX * 2,\n )\n const height = headerHeight + attrHeight + methodHeight\n\n classSizes.set(cls.id, {\n width,\n height,\n headerHeight,\n attrHeight,\n methodHeight,\n })\n }\n\n // Iterate classSizes directly (populated above, in diagram.classes order)\n // rather than looking each class back up by id — sidesteps needing an\n // assertion or invariant check for a lookup that can't actually miss.\n const children: ElkNode[] = []\n for (const [id, size] of classSizes) {\n children.push(buildElkLeafNode(id, size))\n }\n\n // Class edge labels carry no per-label layout options — placement is set\n // once on the root graph below (`elk.edgeLabels.placement: CENTER`).\n const labelStyle = { fontSize: fontSizes.edgeLabel }\n\n const edges: ElkExtendedEdge[] = []\n for (const [i, rel] of diagram.relationships.entries()) {\n edges.push(\n buildElkEdge({\n id: `e${i}`,\n source: rel.from,\n target: rel.to,\n label: rel.label,\n labelStyle,\n }),\n )\n }\n\n // Notes. Mirrors Mermaid's classDb.getData(): every note is a node of its\n // own, and `note for X` adds an arrowless dotted edge note→class so the\n // layout keeps the two adjacent (with a DOWN layout the note lands above\n // its class, as it does in Mermaid's TB rendering). The link edges go\n // after the relationship edges so relationship indices stay positional.\n const classIds = new Set(classSizes.keys())\n for (const [i, note] of diagram.notes.entries()) {\n const id = classNoteId(i)\n const metrics = measureMultilineText(\n note.text,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n const size = {\n width: metrics.width + CLS.notePadX * 2,\n height: metrics.height + CLS.notePadY * 2,\n }\n noteSizes.set(id, size)\n children.push(buildElkLeafNode(id, size))\n if (note.forClass !== undefined && classIds.has(note.forClass)) {\n edges.push(\n buildElkEdge({\n id: classNoteLinkId(id),\n source: id,\n target: note.forClass,\n labelStyle,\n }),\n )\n }\n }\n\n const elkGraph: ElkNode = {\n id: 'root',\n layoutOptions: {\n ...baseElkLayoutOptions({\n // Class diagrams have no `direction` concept — they always lay out\n // top-down. See ELK_DIRECTION_FALLBACK for why that default is\n // per-diagram-type rather than shared with ER's.\n direction: directionToElk(undefined, ELK_DIRECTION_FALLBACK.class),\n nodeSpacing: CLS.nodeSpacing,\n layerSpacing: CLS.layerSpacing,\n padding: CLS.padding,\n }),\n 'elk.edgeLabels.placement': 'CENTER',\n },\n children,\n edges,\n }\n\n return { elkGraph, classSizes, noteSizes }\n}\n\n/** Extract positioned classes, relationships, and notes from ELK result. */\nfunction extractClassLayout(\n result: ElkNode,\n diagram: ClassDiagram,\n classSizes: ClassSizeMap,\n noteSizes: NoteSizeMap,\n): PositionedClassDiagram {\n const classLookup = new Map<string, ClassNode>()\n for (const cls of diagram.classes) classLookup.set(cls.id, cls)\n\n const positionedClasses: PositionedClassNode[] = []\n for (const child of result.children ?? []) {\n const cls = classLookup.get(child.id)\n if (cls) {\n const size = classSizes.get(cls.id)\n if (!size) {\n // Unreachable — classSizes is populated for every diagram.classes\n // entry, and classLookup/cls.id come from that same list.\n /* v8 ignore next */\n throw new Error(`Missing computed size for class \"${cls.id}\"`)\n }\n positionedClasses.push({\n id: cls.id,\n label: cls.label,\n annotation: cls.annotation,\n attributes: cls.attributes,\n methods: cls.methods,\n x: child.x ?? 0,\n y: child.y ?? 0,\n width: child.width ?? size.width,\n height: child.height ?? size.height,\n headerHeight: size.headerHeight,\n attrHeight: size.attrHeight,\n methodHeight: size.methodHeight,\n interaction: diagram.interactions.get(cls.id),\n // Same cascade the flowchart layout applies (src/layout-engine/\n // from-elk.ts): classDef default → assigned class → `style`.\n inlineStyle: resolveNodeStyle(cls.id, diagram),\n // Kept separately from inlineStyle so the class name still reaches\n // the SVG `class` attribute when it has no matching classDef.\n className: diagram.classAssignments.get(cls.id),\n })\n }\n }\n\n const relationships: PositionedClassRelationship[] = []\n const elkEdges = result.edges ?? []\n // The first diagram.relationships.length edges are the relationships, in\n // order — buildClassElkGraph creates exactly one ELK edge per relationship\n // before appending any note links.\n for (const [i, rel] of diagram.relationships.entries()) {\n const elkEdge = elkEdges[i]\n if (!elkEdge) {\n // Unreachable — ELK returns every edge it was given.\n /* v8 ignore next */\n throw new Error(`Missing ELK edge for relationship ${i}`)\n }\n\n const points = extractEdgePoints(elkEdge)\n const labelPosition = extractEdgeLabelPosition(elkEdge)\n\n relationships.push({\n from: rel.from,\n to: rel.to,\n type: rel.type,\n markerAt: rel.markerAt,\n label: rel.label,\n fromCardinality: rel.fromCardinality,\n toCardinality: rel.toCardinality,\n points,\n labelPosition,\n })\n }\n\n // Note links are matched by id rather than position — only notes whose\n // class exists got an edge, so their count isn't derivable from the note list.\n const linkEdges = new Map<string, ElkExtendedEdge>()\n for (const elkEdge of elkEdges.slice(diagram.relationships.length)) {\n linkEdges.set(elkEdge.id, elkEdge)\n }\n const childById = new Map<string, ElkNode>()\n for (const child of result.children ?? []) childById.set(child.id, child)\n\n const notes: PositionedClassNote[] = []\n for (const [i, note] of diagram.notes.entries()) {\n const id = classNoteId(i)\n const child = childById.get(id)\n const size = noteSizes.get(id)\n if (!child || !size) {\n // Unreachable — a child and a size are recorded for every note.\n /* v8 ignore next */\n throw new Error(`Missing layout for note ${i}`)\n }\n const link = linkEdges.get(classNoteLinkId(id))\n notes.push({\n id,\n text: note.text,\n ...(link && note.forClass !== undefined\n ? { forClass: note.forClass }\n : {}),\n x: child.x ?? 0,\n y: child.y ?? 0,\n width: child.width ?? size.width,\n height: child.height ?? size.height,\n ...(link ? { linkPoints: extractEdgePoints(link) } : {}),\n })\n }\n\n return {\n width: result.width ?? 600,\n height: result.height ?? 400,\n classes: positionedClasses,\n relationships,\n notes,\n }\n}\n\n/**\n * Lay out a parsed class diagram using ELK.js (synchronous).\n */\nexport function layoutClassDiagramSync(\n diagram: ClassDiagram,\n options: ClassRenderOptions = {},\n): PositionedClassDiagram {\n if (diagram.classes.length === 0 && diagram.notes.length === 0) {\n return { width: 0, height: 0, classes: [], relationships: [], notes: [] }\n }\n\n const { elkGraph, classSizes, noteSizes } = buildClassElkGraph(\n diagram,\n options,\n )\n const result = elkLayoutSync(elkGraph, options.layoutCache)\n return extractClassLayout(result, diagram, classSizes, noteSizes)\n}\n\n/** Calculate the max width of a list of class members (uses mono metrics) */\nfunction maxMemberWidth(members: ClassMember[]): number {\n if (members.length === 0) return 0\n let maxW = 0\n for (const m of members) {\n const text = formatClassMember(m)\n const w = estimateMonoTextWidth(text, CLS.memberFontSize)\n if (w > maxW) maxW = w\n }\n return maxW\n}\n","import type {\n PositionedClassDiagram,\n PositionedClassNode,\n PositionedClassNote,\n PositionedClassRelationship,\n ClassMember,\n RelationshipType,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { DiagramColors, SvgEmitOptions } from '@zombie-mermaid/core'\nimport {\n svgOpenTag,\n buildStyleBlock,\n getReadableTextColor,\n sanitizeClassName,\n renderMultilineText,\n escapeXml as escapeXmlUtil,\n escapeAttr,\n safeHref,\n f,\n} from '@zombie-mermaid/core'\nimport { withDataSrc } from '../renderer.ts'\nimport {\n FONT_SIZES,\n FONT_WEIGHTS,\n STROKE_WIDTHS,\n TEXT_BASELINE_SHIFT,\n} from '../styles.ts'\nimport type { FontSizes } from '../styles.ts'\nimport { CLS } from './layout.ts'\n\n// ============================================================================\n// Class diagram SVG renderer\n//\n// Renders positioned class diagrams to SVG.\n// All colors use CSS custom properties (var(--_xxx)) from the theme system.\n//\n// Render order:\n// 1. Relationship lines and note links (behind boxes)\n// 2. Class boxes (header + attributes + methods compartments)\n// 3. Notes (dog-eared boxes)\n// 4. Relationship labels and cardinality\n// ============================================================================\n\n/** Font sizes specific to class diagrams */\nconst CLS_FONT = {\n memberSize: 11,\n memberWeight: 400,\n annotationSize: 10,\n annotationWeight: 500,\n} as const\n\n/**\n * Render a positioned class diagram as an SVG string.\n *\n * @param colors - DiagramColors with bg/fg and optional enrichment variables.\n * @param transparent - If true, renders with transparent background.\n * @param embedSource - Original diagram source to stamp onto the root `<svg>`\n * as `data-src` (from `options.embedSource`). Omitted\n * when the option is off.\n * @param title - Accessible name (from `options.title`). See svgOpenTag() in\n * packages/core/src/theme.ts.\n * @param decorative - Marks the SVG decorative (from `options.decorative`).\n * @param linksEnabled - Whether `click`-based `<a href>` links and `<title>`\n * tooltips render (from `options.interactivity !==\n * 'none'`, see `resolveLinksEnabled` in\n * src/diagram-registry.ts).\n * Default true — matches the flowchart/state renderer.\n * @param emit - Strict-CSP controls (from `options.nonce` /\n * `options.styleAttribute`, see #216). Default: no nonce,\n * root `style` attribute on.\n */\nexport function renderClassSvg(\n diagram: PositionedClassDiagram,\n colors: DiagramColors,\n font: string = 'Inter',\n transparent: boolean = false,\n fontSizes: FontSizes = FONT_SIZES,\n embedSource?: string,\n title?: string,\n decorative?: boolean,\n linksEnabled: boolean = true,\n emit: SvgEmitOptions = {},\n): string {\n const parts: string[] = []\n\n // See #239 / packages/svg-renderer/src/renderer.ts's renderSvg: a click-based link renders as a\n // focusable <a href> inside the SVG, which role=\"img\"/aria-hidden would\n // hide from assistive tech while leaving it Tab-reachable — svgOpenTag\n // forces no root role in that case. Gated by linksEnabled too, since\n // `interactivity: 'none'` strips the <a> below.\n const hasInteractiveLinks =\n linksEnabled &&\n diagram.classes.some((c) => Boolean(safeHref(c.interaction?.href)))\n\n // SVG root with CSS variables + style block (with mono font) + defs\n parts.push(\n withDataSrc(\n svgOpenTag(\n diagram.width,\n diagram.height,\n colors,\n transparent,\n title,\n decorative,\n hasInteractiveLinks,\n emit.styleAttribute,\n ),\n embedSource,\n ),\n )\n parts.push(buildStyleBlock(font, true, emit.nonce))\n parts.push('<defs>')\n parts.push(relationshipMarkerDefs())\n parts.push('</defs>')\n\n // 1. Relationship lines and note links (rendered behind boxes)\n for (const rel of diagram.relationships) {\n parts.push(renderRelationship(rel))\n }\n for (const note of diagram.notes) {\n parts.push(renderNoteLink(note))\n }\n\n // 2. Class boxes\n for (const cls of diagram.classes) {\n parts.push(renderClassBox(cls, fontSizes, linksEnabled))\n }\n\n // 3. Notes\n for (const note of diagram.notes) {\n parts.push(renderNote(note, fontSizes))\n }\n\n // 4. Relationship labels and cardinality\n for (const rel of diagram.relationships) {\n parts.push(renderRelationshipLabels(rel, fontSizes))\n }\n\n parts.push('</svg>')\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Marker definitions\n// ============================================================================\n\n/**\n * Marker definitions for class relationship endpoints.\n * Each relationship type has a distinct marker:\n * - inheritance: hollow triangle\n * - composition: filled diamond\n * - aggregation: hollow diamond\n * - association: open arrow (simple >)\n * - dependency: open arrow (simple >)\n * - realization: hollow triangle (same as inheritance)\n *\n * Uses var(--_arrow) for fill/stroke and var(--bg) for hollow marker fills.\n */\nfunction relationshipMarkerDefs(): string {\n return (\n // Hollow triangle (inheritance, realization) — points at target\n ` <marker id=\"cls-inherit\" markerWidth=\"12\" markerHeight=\"10\" refX=\"12\" refY=\"5\" orient=\"auto-start-reverse\">` +\n `\\n <polygon points=\"0 0, 12 5, 0 10\" fill=\"var(--bg)\" stroke=\"var(--_arrow)\" stroke-width=\"1.5\" />` +\n `\\n </marker>` +\n // Filled diamond (composition) — tip sits on the endpoint, body runs along the line;\n // the polygon is inset so the stroke isn't clipped by the marker viewport\n `\\n <marker id=\"cls-composition\" markerWidth=\"18\" markerHeight=\"14\" refX=\"16.5\" refY=\"7\" orient=\"auto-start-reverse\">` +\n `\\n <polygon points=\"9 1.5, 16.5 7, 9 12.5, 1.5 7\" fill=\"var(--_arrow)\" stroke=\"var(--_arrow)\" stroke-width=\"1\" />` +\n `\\n </marker>` +\n // Hollow diamond (aggregation) — tip sits on the endpoint, body runs along the line\n `\\n <marker id=\"cls-aggregation\" markerWidth=\"18\" markerHeight=\"14\" refX=\"16.5\" refY=\"7\" orient=\"auto-start-reverse\">` +\n `\\n <polygon points=\"9 1.5, 16.5 7, 9 12.5, 1.5 7\" fill=\"var(--bg)\" stroke=\"var(--_arrow)\" stroke-width=\"1.5\" />` +\n `\\n </marker>` +\n // Open arrow (association, dependency)\n `\\n <marker id=\"cls-arrow\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto-start-reverse\">` +\n `\\n <polyline points=\"0 0, 8 3, 0 6\" fill=\"none\" stroke=\"var(--_arrow)\" stroke-width=\"1.5\" />` +\n `\\n </marker>`\n )\n}\n\n// ============================================================================\n// Class box rendering\n// ============================================================================\n\n/**\n * Render a class box with 3 compartments: header, attributes, methods.\n * Wrapped in <g class=\"class-node\"> with semantic data attributes.\n *\n * @param linksEnabled - Whether a `click`-based `<a href>` link and `<title>`\n * tooltip render (from `options.interactivity !==\n * 'none'`). Default true. Mirrors renderNode() in\n * packages/svg-renderer/src/renderer.ts — see that function's docstring and\n * docs/decisions/no-script-interactivity.md for why a\n * `call`/callback binding is recorded as a data\n * attribute rather than ever invoked.\n */\nfunction renderClassBox(\n cls: PositionedClassNode,\n fontSizes: FontSizes,\n linksEnabled: boolean = true,\n): string {\n const { x, y, width, height, headerHeight, attrHeight, inlineStyle } = cls\n const parts: string[] = []\n\n const interaction = cls.interaction\n\n // Resolve box colors — inline styles (from `classDef`/`cssClass`/`style`)\n // override the CSS-variable defaults the same way renderNodeShape() in\n // packages/svg-renderer/src/renderer.ts does for flowchart nodes. With no inline style the\n // variables keep deriving from the theme via color-mix(), so dark mode is\n // untouched; a concrete `fill` is used verbatim, exactly as Mermaid does.\n const fill = escapeAttr(inlineStyle?.fill ?? 'var(--_node-fill)')\n const stroke = escapeAttr(inlineStyle?.stroke ?? 'var(--_node-stroke)')\n const strokeWidth = escapeAttr(\n inlineStyle?.['stroke-width'] ?? String(STROKE_WIDTHS.outerBox),\n )\n // Mermaid paints the whole class box one color, so a custom fill replaces\n // the header band too rather than leaving the theme's band on top of it.\n const headerFill = inlineStyle?.fill ? fill : 'var(--_group-hdr)'\n // An explicit `color:` wins; otherwise a concrete custom fill gets a\n // black/white text color chosen for contrast (see getReadableTextColor,\n // issue #55). Left undefined when neither applies so the syntax-colored\n // member tspans keep their theme defaults.\n const textColor = inlineStyle?.color\n ? escapeAttr(inlineStyle.color)\n : inlineStyle?.fill\n ? escapeAttr(getReadableTextColor(inlineStyle.fill, 'var(--_text)'))\n : undefined\n\n // Semantic wrapper with class metadata\n // data-id: class identifier\n // data-label: class name\n // data-annotation: stereotype (interface, abstract, etc.)\n const annotationAttr = cls.annotation\n ? f` data-annotation=\"${escapeAttr(cls.annotation)}\"`\n : ''\n // A style class from `cssClass`/`class A name`/`:::name` is emitted onto\n // the group so external CSS can target it, after the same allowlist the\n // flowchart renderer applies.\n const safeClassName = sanitizeClassName(cls.className)\n const groupAttrs = [\n f`class=\"${safeClassName ? f`class-node ${safeClassName}` : 'class-node'}\"`,\n f`data-id=\"${escapeAttr(cls.id)}\"`,\n f`data-label=\"${escapeAttr(cls.label)}\"`,\n ]\n // `click ClassName call fn()` is parsed, never invoked, and never written\n // into the markup — see renderNode() in packages/svg-renderer/src/renderer.ts for the rationale.\n parts.push(f`<g ${groupAttrs.join(' ')}${annotationAttr}>`)\n\n // An href becomes a real SVG link, which needs no script to work.\n // `interactivity: 'none'` strips it — a link is meaningless in\n // print/rasterized output, the target that level is meant for.\n const href = linksEnabled ? safeHref(interaction?.href) : undefined\n if (href) {\n const targetAttr = interaction?.target\n ? f` target=\"${escapeAttr(interaction.target)}\"`\n : ''\n parts.push(f` <a href=\"${escapeAttr(href)}\"${targetAttr}>`)\n }\n\n if (linksEnabled && interaction?.tooltip) {\n // <title> is SVG's native tooltip — no script, no CSS. Gated by\n // linksEnabled alongside href above: both come from the same `click`\n // statement, and 'none' strips both.\n parts.push(f` <title>${escapeXml(interaction.tooltip)}</title>`)\n }\n\n // Outer rectangle (full box)\n parts.push(\n f` <rect x=\"${x}\" y=\"${y}\" width=\"${width}\" height=\"${height}\" ` +\n f`rx=\"0\" ry=\"0\" fill=\"${fill}\" stroke=\"${stroke}\" stroke-width=\"${strokeWidth}\" />`,\n )\n\n // Header background\n parts.push(\n f` <rect x=\"${x}\" y=\"${y}\" width=\"${width}\" height=\"${headerHeight}\" ` +\n f`rx=\"0\" ry=\"0\" fill=\"${headerFill}\" stroke=\"${stroke}\" stroke-width=\"${strokeWidth}\" />`,\n )\n\n // Annotation (<<interface>>, <<abstract>>, etc.)\n let nameY = y + headerHeight / 2\n if (cls.annotation) {\n const annotY = y + 12\n parts.push(\n f` <text x=\"${x + width / 2}\" y=\"${annotY}\" text-anchor=\"middle\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n f`font-size=\"${CLS_FONT.annotationSize}\" font-weight=\"${CLS_FONT.annotationWeight}\" ` +\n f`font-style=\"italic\" fill=\"${textColor ?? 'var(--_text-muted)'}\">&lt;&lt;${escapeXml(cls.annotation)}&gt;&gt;</text>`,\n )\n nameY = y + headerHeight / 2 + 6\n }\n\n // Class name (supports multi-line via <br> tags)\n parts.push(\n ' ' +\n renderMultilineText(\n cls.label,\n x + width / 2,\n nameY,\n fontSizes.nodeLabel,\n f`text-anchor=\"middle\" font-size=\"${fontSizes.nodeLabel}\" font-weight=\"700\" fill=\"${textColor ?? 'var(--_text)'}\"`,\n ),\n )\n\n // Divider line between header and attributes\n const attrTop = y + headerHeight\n parts.push(\n f` <line x1=\"${x}\" y1=\"${attrTop}\" x2=\"${x + width}\" y2=\"${attrTop}\" ` +\n f`stroke=\"${stroke}\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />`,\n )\n\n // Attributes\n const memberRowH = 20\n for (let i = 0; i < cls.attributes.length; i++) {\n const member = cls.attributes[i]!\n const memberY = attrTop + 4 + i * memberRowH + memberRowH / 2\n parts.push(' ' + renderMember(member, x + CLS.boxPadX, memberY, textColor))\n }\n\n // Divider line between attributes and methods\n const methodTop = attrTop + attrHeight\n parts.push(\n f` <line x1=\"${x}\" y1=\"${methodTop}\" x2=\"${x + width}\" y2=\"${methodTop}\" ` +\n f`stroke=\"${stroke}\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />`,\n )\n\n // Methods\n for (let i = 0; i < cls.methods.length; i++) {\n const member = cls.methods[i]!\n const memberY = methodTop + 4 + i * memberRowH + memberRowH / 2\n parts.push(' ' + renderMember(member, x + CLS.boxPadX, memberY, textColor))\n }\n\n if (href) parts.push(' </a>')\n parts.push('</g>')\n\n return parts.join('\\n')\n}\n\n/**\n * Render a single class member with syntax highlighting.\n * Uses <tspan> elements to color each part of the member differently:\n * - visibility symbol (+/-/#/~) → textFaint\n * - member name (incl. parens for methods) → textSecondary\n * - colon separator → textFaint\n * - type annotation → textMuted\n *\n * @param textColor - A resolved custom text color (from `style ... color:` or\n * derived from a custom `fill`). When given, every tspan\n * uses it: the per-part theme tints are tuned against the\n * theme's own node fill and can't be assumed readable on\n * an arbitrary user-chosen background.\n */\nfunction renderMember(\n member: ClassMember,\n x: number,\n y: number,\n textColor?: string,\n): string {\n const fontStyle = member.isAbstract ? ' font-style=\"italic\"' : ''\n const decoration = member.isStatic ? ' text-decoration=\"underline\"' : ''\n\n const faint = textColor ?? 'var(--_text-faint)'\n const secondary = textColor ?? 'var(--_text-sec)'\n const muted = textColor ?? 'var(--_text-muted)'\n\n // Build tspan parts for syntax-highlighted member text\n const spans: string[] = []\n\n if (member.visibility) {\n spans.push(\n f`<tspan fill=\"${faint}\">${escapeXml(member.visibility)} </tspan>`,\n )\n }\n\n // Add parentheses for methods to distinguish from attributes, including parameters if present\n const displayName = member.isMethod\n ? f`${member.name}(${member.params || ''})`\n : member.name\n // False positive: displayName is passed through escapeXml() (see packages/core/src/multiline-utils.ts),\n // which escapes &, <, >, \", ' before interpolation, so this is not raw/unescaped HTML.\n spans.push(f`<tspan fill=\"${secondary}\">${escapeXml(displayName)}</tspan>`) // nosemgrep: javascript.express.security.injection.raw-html-format.raw-html-format\n\n if (member.type) {\n spans.push(f`<tspan fill=\"${faint}\">: </tspan>`)\n spans.push(f`<tspan fill=\"${muted}\">${escapeXml(member.type)}</tspan>`)\n }\n\n return (\n f`<text x=\"${x}\" y=\"${y}\" class=\"mono\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n f`font-size=\"${CLS_FONT.memberSize}\" font-weight=\"${CLS_FONT.memberWeight}\"${fontStyle}${decoration}>` +\n f`${spans.join('')}</text>`\n )\n}\n\n// ============================================================================\n// Note rendering\n// ============================================================================\n\n/** Size of the folded corner on a note box, in px */\nconst NOTE_FOLD = 6\n\n/**\n * Render a note as a dog-eared box: a polygon with its top-right corner\n * clipped plus a small fold triangle — the same shape the sequence renderer\n * draws for its notes (../sequence/renderer.ts renderNote), so notes look\n * alike across diagram types. Wrapped in <g class=\"class-note\"> with\n * `data-for` naming the class it's attached to, if any.\n */\nfunction renderNote(note: PositionedClassNote, fontSizes: FontSizes): string {\n const { x, y, width: w, height: h } = note\n const forAttr =\n note.forClass !== undefined\n ? f` data-for=\"${escapeAttr(note.forClass)}\"`\n : ''\n\n // Note body: (x,y) → (x+w-fold,y) → (x+w,y+fold) → (x+w,y+h) → (x,y+h)\n const bodyPoints = [\n f`${x},${y}`,\n f`${x + w - NOTE_FOLD},${y}`,\n f`${x + w},${y + NOTE_FOLD}`,\n f`${x + w},${y + h}`,\n f`${x},${y + h}`,\n ].join(' ')\n const foldPoints =\n f`${x + w - NOTE_FOLD},${y} ${x + w},${y + NOTE_FOLD} ` +\n f`${x + w - NOTE_FOLD},${y + NOTE_FOLD}`\n\n return (\n f`<g class=\"class-note\"${forAttr}>` +\n f`\\n <polygon points=\"${bodyPoints}\" ` +\n f`fill=\"var(--bg)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />` +\n f`\\n <polygon points=\"${foldPoints}\" ` +\n f`fill=\"var(--_inner-stroke)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />` +\n f`\\n ${renderMultilineText(\n note.text,\n x + w / 2,\n y + h / 2,\n fontSizes.edgeLabel,\n f`font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n )}` +\n `\\n</g>`\n )\n}\n\n/**\n * Render the dotted, arrowless link from a `note for X` note to its class\n * (Mermaid's `pattern: 'dotted'` note edge). Empty for a free note.\n */\nfunction renderNoteLink(note: PositionedClassNote): string {\n if (!note.linkPoints || note.linkPoints.length < 2) return ''\n const pathData = note.linkPoints.map((p) => f`${p.x},${p.y}`).join(' ')\n const forAttr =\n note.forClass !== undefined\n ? f` data-for=\"${escapeAttr(note.forClass)}\"`\n : ''\n return (\n f`<polyline class=\"class-note-link\"${forAttr} points=\"${pathData}\" ` +\n f`fill=\"none\" stroke=\"var(--_line)\" stroke-width=\"${STROKE_WIDTHS.connector}\" stroke-dasharray=\"2 3\" />`\n )\n}\n\n// ============================================================================\n// Relationship rendering\n// ============================================================================\n\n/**\n * Render a relationship line with appropriate markers and semantic attributes.\n * Includes data-* attributes for programmatic inspection.\n */\nfunction renderRelationship(rel: PositionedClassRelationship): string {\n if (rel.points.length < 2) return ''\n\n const pathData = rel.points.map((p) => f`${p.x},${p.y}`).join(' ')\n const isDashed = rel.type === 'dependency' || rel.type === 'realization'\n const dashArray = isDashed ? ' stroke-dasharray=\"6 4\"' : ''\n\n // Determine markers based on relationship type and which end has the marker\n const markers = getRelationshipMarkers(rel.type, rel.markerAt)\n\n // Build semantic data attributes for relationship inspection:\n // - class=\"class-relationship\": CSS targeting\n // - data-from/data-to: source and target class IDs\n // - data-type: relationship type (inheritance, composition, etc.)\n // - data-marker-at: which end has the marker (from/to)\n // - data-from-cardinality/data-to-cardinality: multiplicity if present\n // - data-label: relationship label if present\n const dataAttrs = [\n 'class=\"class-relationship\"',\n f`data-from=\"${escapeAttr(rel.from)}\"`,\n f`data-to=\"${escapeAttr(rel.to)}\"`,\n f`data-type=\"${rel.type}\"`,\n f`data-marker-at=\"${rel.markerAt}\"`,\n ]\n if (rel.label) {\n dataAttrs.push(f`data-label=\"${escapeAttr(rel.label)}\"`)\n }\n if (rel.fromCardinality) {\n dataAttrs.push(\n f`data-from-cardinality=\"${escapeAttr(rel.fromCardinality)}\"`,\n )\n }\n if (rel.toCardinality) {\n dataAttrs.push(f`data-to-cardinality=\"${escapeAttr(rel.toCardinality)}\"`)\n }\n\n return (\n f`<polyline ${dataAttrs.join(' ')} points=\"${pathData}\" fill=\"none\" stroke=\"var(--_line)\" ` +\n f`stroke-width=\"${STROKE_WIDTHS.connector}\"${dashArray}${markers} />`\n )\n}\n\n/**\n * Get marker-start/marker-end attributes for a relationship type.\n * Uses `markerAt` from the parser to place the marker on the correct end:\n * - 'from' → marker-start (prefix arrows like `<|--`, `*--`, `o--`)\n * - 'to' → marker-end (suffix arrows like `..|>`, `-->`, `--*`)\n */\nfunction getRelationshipMarkers(\n type: RelationshipType,\n markerAt: 'from' | 'to',\n): string {\n const markerId = getMarkerDefId(type)\n if (!markerId) return ''\n\n if (markerAt === 'from') {\n return f` marker-start=\"url(#${markerId})\"`\n } else {\n return f` marker-end=\"url(#${markerId})\"`\n }\n}\n\n/** Map relationship type to its SVG marker definition ID */\nfunction getMarkerDefId(type: RelationshipType): string | null {\n switch (type) {\n case 'inheritance':\n case 'realization':\n return 'cls-inherit'\n case 'composition':\n return 'cls-composition'\n case 'aggregation':\n return 'cls-aggregation'\n case 'association':\n case 'dependency':\n return 'cls-arrow'\n default:\n return null\n }\n}\n\n/** Render relationship labels and cardinality text (supports multi-line) */\nfunction renderRelationshipLabels(\n rel: PositionedClassRelationship,\n fontSizes: FontSizes,\n): string {\n if (!rel.label && !rel.fromCardinality && !rel.toCardinality) return ''\n if (rel.points.length < 2) return ''\n\n const parts: string[] = []\n\n // Label — prefer layout-computed position (collision-aware), fall back to midpoint\n if (rel.label) {\n const pos = rel.labelPosition ?? midpoint(rel.points)\n parts.push(\n renderMultilineText(\n rel.label,\n pos.x,\n pos.y - 8,\n fontSizes.edgeLabel,\n f`font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n }\n\n // From cardinality (near start)\n if (rel.fromCardinality && rel.points.length >= 2) {\n const p = rel.points[0]!\n const next = rel.points[1]!\n const offset = cardinalityOffset(p, next)\n parts.push(\n renderMultilineText(\n rel.fromCardinality,\n p.x + offset.x,\n p.y + offset.y,\n fontSizes.edgeLabel,\n f`font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n }\n\n // To cardinality (near end)\n if (rel.toCardinality && rel.points.length >= 2) {\n const p = rel.points[rel.points.length - 1]!\n const prev = rel.points[rel.points.length - 2]!\n const offset = cardinalityOffset(p, prev)\n parts.push(\n renderMultilineText(\n rel.toCardinality,\n p.x + offset.x,\n p.y + offset.y,\n fontSizes.edgeLabel,\n f`font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n }\n\n return parts.join('\\n')\n}\n\n/** Get the midpoint of a point array */\nfunction midpoint(points: Array<{ x: number; y: number }>): {\n x: number\n y: number\n} {\n if (points.length === 0) return { x: 0, y: 0 }\n const mid = Math.floor(points.length / 2)\n return points[mid]!\n}\n\n/** Calculate offset for cardinality label perpendicular to edge direction */\nfunction cardinalityOffset(\n from: { x: number; y: number },\n to: { x: number; y: number },\n): { x: number; y: number } {\n const dx = to.x - from.x\n const dy = to.y - from.y\n // Place label perpendicular to the edge, 14px away\n if (Math.abs(dx) > Math.abs(dy)) {\n // Mostly horizontal — offset vertically\n return { x: dx > 0 ? 14 : -14, y: -10 }\n }\n // Mostly vertical — offset horizontally\n return { x: -14, y: dy > 0 ? 14 : -14 }\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n// Use shared escapeXml from multiline-utils\nconst escapeXml = escapeXmlUtil\n","/**\n * ER diagram layout engine (ELK.js).\n *\n * Each entity box has:\n * 1. Header (entity name)\n * 2. Attribute rows (type, name, keys)\n */\n\nimport type { ElkNode, ElkExtendedEdge } from 'elkjs'\nimport type {\n ErDiagram,\n ErEntity,\n PositionedErDiagram,\n PositionedErEntity,\n PositionedErRelationship,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { ErRenderOptions } from '@zombie-mermaid/core'\nimport {\n estimateTextWidth,\n estimateMonoTextWidth,\n FONT_WEIGHTS,\n resolveFontSizes,\n} from '../styles.ts'\nimport { elkLayoutSync } from '../elk-instance.ts'\nimport { extractEdgePoints } from '../layout-engine/elk-adapter-utils.ts'\nimport {\n ELK_DIRECTION_FALLBACK,\n baseElkLayoutOptions,\n buildElkEdge,\n buildElkLeafNode,\n directionToElk,\n} from '../layout-engine/elk-graph-builder.ts'\n\n/** Layout constants for ER diagrams */\nconst ER = {\n padding: 40,\n boxPadX: 14,\n headerHeight: 34,\n rowHeight: 22,\n minWidth: 140,\n attrFontSize: 11,\n attrFontWeight: 400,\n nodeSpacing: 70,\n layerSpacing: 90,\n} as const\n\ntype EntitySizeMap = Map<string, { width: number; height: number }>\n\n/** Build ELK graph and size map from an ER diagram. */\nfunction buildErElkGraph(\n diagram: ErDiagram,\n options: ErRenderOptions,\n): { elkGraph: ElkNode; entitySizes: EntitySizeMap } {\n const entitySizes: EntitySizeMap = new Map()\n const fontSizes = resolveFontSizes(options.fontSizes)\n\n for (const entity of diagram.entities) {\n const headerTextW = estimateTextWidth(\n entity.label,\n fontSizes.nodeLabel,\n FONT_WEIGHTS.nodeLabel,\n )\n let maxAttrW = 0\n for (const attr of entity.attributes) {\n const attrText = `${attr.type} ${attr.name}${attr.keys.length > 0 ? ' ' + attr.keys.join(',') : ''}`\n const w = estimateMonoTextWidth(attrText, ER.attrFontSize)\n if (w > maxAttrW) maxAttrW = w\n }\n const width = Math.max(\n ER.minWidth,\n headerTextW + ER.boxPadX * 2,\n maxAttrW + ER.boxPadX * 2,\n )\n const height =\n ER.headerHeight + Math.max(entity.attributes.length, 1) * ER.rowHeight\n entitySizes.set(entity.id, { width, height })\n }\n\n // Iterate entitySizes directly (populated above, in diagram.entities order)\n // rather than looking each entity back up by id — sidesteps needing an\n // assertion or invariant check for a lookup that can't actually miss.\n const children: ElkNode[] = []\n for (const [id, size] of entitySizes) {\n children.push(buildElkLeafNode(id, size))\n }\n\n // ER edge labels carry no per-label layout options — placement is set\n // once on the root graph below (`elk.edgeLabels.placement: CENTER`).\n const labelStyle = { fontSize: fontSizes.edgeLabel }\n\n const edges: ElkExtendedEdge[] = []\n for (const [i, rel] of diagram.relationships.entries()) {\n edges.push(\n buildElkEdge({\n id: `e${i}`,\n source: rel.entity1,\n target: rel.entity2,\n label: rel.label,\n labelStyle,\n }),\n )\n }\n\n const elkGraph: ElkNode = {\n id: 'root',\n layoutOptions: {\n ...baseElkLayoutOptions({\n // A source with no `direction` statement lays out left-to-right —\n // ER's own default, unlike flowchart/class's DOWN. See\n // ELK_DIRECTION_FALLBACK.\n direction: directionToElk(diagram.direction, ELK_DIRECTION_FALLBACK.er),\n nodeSpacing: ER.nodeSpacing,\n layerSpacing: ER.layerSpacing,\n padding: ER.padding,\n }),\n 'elk.edgeLabels.placement': 'CENTER',\n },\n children,\n edges,\n }\n\n return { elkGraph, entitySizes }\n}\n\n/** Extract positioned entities and relationships from ELK result. */\nfunction extractErLayout(\n result: ElkNode,\n diagram: ErDiagram,\n entitySizes: EntitySizeMap,\n): PositionedErDiagram {\n const entityLookup = new Map<string, ErEntity>()\n for (const entity of diagram.entities) entityLookup.set(entity.id, entity)\n\n const positionedEntities: PositionedErEntity[] = []\n for (const child of result.children ?? []) {\n const entity = entityLookup.get(child.id)\n if (entity) {\n const size = entitySizes.get(entity.id)\n if (!size) {\n // Unreachable — entitySizes is populated for every diagram.entities\n // entry, and entityLookup/entity.id come from that same list.\n /* v8 ignore next */\n throw new Error(`Missing computed size for entity \"${entity.id}\"`)\n }\n positionedEntities.push({\n id: entity.id,\n label: entity.label,\n attributes: entity.attributes,\n x: child.x ?? 0,\n y: child.y ?? 0,\n width: child.width ?? size.width,\n height: child.height ?? size.height,\n headerHeight: ER.headerHeight,\n rowHeight: ER.rowHeight,\n })\n }\n }\n\n const relationships: PositionedErRelationship[] = []\n for (const [i, elkEdge] of (result.edges ?? []).entries()) {\n // diagram.relationships[i] is guaranteed by construction: buildErElkGraph\n // creates exactly one ELK edge per relationship, in the same order.\n const rel = diagram.relationships[i]!\n\n const points = extractEdgePoints(elkEdge)\n\n relationships.push({\n entity1: rel.entity1,\n entity2: rel.entity2,\n cardinality1: rel.cardinality1,\n cardinality2: rel.cardinality2,\n label: rel.label,\n identifying: rel.identifying,\n points,\n })\n }\n\n return {\n width: result.width ?? 600,\n height: result.height ?? 400,\n entities: positionedEntities,\n relationships,\n }\n}\n\n/**\n * Lay out a parsed ER diagram using ELK.js (synchronous).\n */\nexport function layoutErDiagramSync(\n diagram: ErDiagram,\n options: ErRenderOptions = {},\n): PositionedErDiagram {\n if (diagram.entities.length === 0) {\n return { width: 0, height: 0, entities: [], relationships: [] }\n }\n\n const { elkGraph, entitySizes } = buildErElkGraph(diagram, options)\n const result = elkLayoutSync(elkGraph, options.layoutCache)\n return extractErLayout(result, diagram, entitySizes)\n}\n","import type {\n PositionedErDiagram,\n PositionedErEntity,\n PositionedErRelationship,\n ErAttribute,\n Cardinality,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { DiagramColors, SvgEmitOptions } from '@zombie-mermaid/core'\nimport {\n svgOpenTag,\n buildStyleBlock,\n renderMultilineText,\n escapeXml as escapeXmlUtil,\n escapeAttr,\n measureMultilineText,\n f,\n} from '@zombie-mermaid/core'\nimport { withDataSrc } from '../renderer.ts'\nimport {\n FONT_SIZES,\n FONT_WEIGHTS,\n STROKE_WIDTHS,\n estimateTextWidth,\n TEXT_BASELINE_SHIFT,\n} from '../styles.ts'\nimport type { FontSizes } from '../styles.ts'\n\n// ============================================================================\n// ER diagram SVG renderer\n//\n// Renders positioned ER diagrams to SVG.\n// All colors use CSS custom properties (var(--_xxx)) from the theme system.\n//\n// Render order:\n// 1. Relationship lines (behind boxes)\n// 2. Entity boxes (header + attribute rows)\n// 3. Cardinality markers (crow's foot notation)\n// 4. Relationship labels\n// ============================================================================\n\n/** Font sizes specific to ER diagrams */\nconst ER_FONT = {\n attrSize: 11,\n attrWeight: 400,\n keySize: 9,\n keyWeight: 600,\n} as const\n\n/**\n * Render a positioned ER diagram as an SVG string.\n *\n * @param colors - DiagramColors with bg/fg and optional enrichment variables.\n * @param transparent - If true, renders with transparent background.\n * @param embedSource - Original diagram source to stamp onto the root `<svg>`\n * as `data-src` (from `options.embedSource`). Omitted\n * when the option is off.\n * @param title - Accessible name (from `options.title`). See svgOpenTag() in\n * packages/core/src/theme.ts.\n * @param decorative - Marks the SVG decorative (from `options.decorative`).\n * @param emit - Strict-CSP controls (from `options.nonce` /\n * `options.styleAttribute`, see #216). Default: no nonce,\n * root `style` attribute on.\n */\nexport function renderErSvg(\n diagram: PositionedErDiagram,\n colors: DiagramColors,\n font: string = 'Inter',\n transparent: boolean = false,\n fontSizes: FontSizes = FONT_SIZES,\n embedSource?: string,\n title?: string,\n decorative?: boolean,\n emit: SvgEmitOptions = {},\n): string {\n const parts: string[] = []\n\n // SVG root with CSS variables + style block (with mono font) + defs\n parts.push(\n withDataSrc(\n svgOpenTag(\n diagram.width,\n diagram.height,\n colors,\n transparent,\n title,\n decorative,\n undefined,\n emit.styleAttribute,\n ),\n embedSource,\n ),\n )\n parts.push(buildStyleBlock(font, true, emit.nonce))\n parts.push('<defs>')\n parts.push('</defs>') // No marker defs — we draw crow's foot inline\n\n // 1. Relationship lines\n for (const rel of diagram.relationships) {\n parts.push(renderRelationshipLine(rel))\n }\n\n // 2. Entity boxes\n for (const entity of diagram.entities) {\n parts.push(renderEntityBox(entity, fontSizes))\n }\n\n // 3. Cardinality markers at relationship endpoints\n for (const rel of diagram.relationships) {\n parts.push(renderCardinality(rel))\n }\n\n // 4. Relationship labels\n for (const rel of diagram.relationships) {\n parts.push(renderRelationshipLabel(rel, fontSizes))\n }\n\n parts.push('</svg>')\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Entity box rendering\n// ============================================================================\n\n/**\n * Render an entity box with header and attribute rows.\n * Wrapped in <g class=\"entity\"> with semantic data attributes.\n */\nfunction renderEntityBox(\n entity: PositionedErEntity,\n fontSizes: FontSizes,\n): string {\n const {\n id,\n x,\n y,\n width,\n height,\n headerHeight,\n rowHeight,\n label,\n attributes,\n } = entity\n const parts: string[] = []\n\n // Semantic wrapper with entity metadata\n parts.push(\n f`<g class=\"entity\" data-id=\"${escapeAttr(id)}\" data-label=\"${escapeAttr(label)}\">`,\n )\n\n // Outer rectangle\n parts.push(\n f` <rect x=\"${x}\" y=\"${y}\" width=\"${width}\" height=\"${height}\" ` +\n f`rx=\"0\" ry=\"0\" fill=\"var(--_node-fill)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n\n // Header background\n parts.push(\n f` <rect x=\"${x}\" y=\"${y}\" width=\"${width}\" height=\"${headerHeight}\" ` +\n f`rx=\"0\" ry=\"0\" fill=\"var(--_group-hdr)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n\n // Entity name (supports multi-line via <br> tags)\n parts.push(\n ' ' +\n renderMultilineText(\n label,\n x + width / 2,\n y + headerHeight / 2,\n fontSizes.nodeLabel,\n f`text-anchor=\"middle\" font-size=\"${fontSizes.nodeLabel}\" font-weight=\"700\" fill=\"var(--_text)\"`,\n ),\n )\n\n // Divider\n const attrTop = y + headerHeight\n parts.push(\n f` <line x1=\"${x}\" y1=\"${attrTop}\" x2=\"${x + width}\" y2=\"${attrTop}\" ` +\n f`stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />`,\n )\n\n // Attribute rows\n for (let i = 0; i < attributes.length; i++) {\n const attr = attributes[i]!\n const rowY = attrTop + i * rowHeight + rowHeight / 2\n parts.push(\n ' ' + renderAttribute(attr, x, rowY, width).replace(/\\n/g, '\\n '),\n )\n }\n\n // Empty row placeholder when no attributes\n if (attributes.length === 0) {\n parts.push(\n f` <text x=\"${x + width / 2}\" y=\"${attrTop + rowHeight / 2}\" text-anchor=\"middle\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n f`font-size=\"${ER_FONT.attrSize}\" fill=\"var(--_text-faint)\" font-style=\"italic\">(no attributes)</text>`,\n )\n }\n\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n/**\n * Render a single attribute row with monospace syntax highlighting.\n * Layout: [PK badge] type name (left-aligned in mono, name right-aligned)\n * Uses <tspan> elements for per-part coloring, matching the class diagram style.\n *\n * Key badge uses var(--_key-badge) for background tint.\n * Comments are shown as tooltips via SVG <title> element.\n */\nfunction renderAttribute(\n attr: ErAttribute,\n boxX: number,\n y: number,\n boxWidth: number,\n): string {\n const parts: string[] = []\n\n // Wrap in a group if there's a comment (for tooltip support)\n const hasComment = attr.comment && attr.comment.length > 0\n if (attr.comment && attr.comment.length > 0) {\n // Replace <br> with newlines for tooltip display\n const tooltipText = attr.comment.replace(/<br\\s*\\/?>/gi, '\\n')\n parts.push(f`<g><title>${escapeXml(tooltipText)}</title>`)\n }\n\n // Key badges on the left (keep proportional font — they're visual tags, not code)\n let keyWidth = 0\n if (attr.keys.length > 0) {\n const keyText = attr.keys.join(',')\n keyWidth =\n estimateTextWidth(keyText, ER_FONT.keySize, ER_FONT.keyWeight) + 8\n parts.push(\n f`<rect x=\"${boxX + 6}\" y=\"${y - 7}\" width=\"${keyWidth}\" height=\"14\" rx=\"2\" ry=\"2\" ` +\n `fill=\"var(--_key-badge)\" />`,\n )\n parts.push(\n f`<text x=\"${boxX + 6 + keyWidth / 2}\" y=\"${y}\" text-anchor=\"middle\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n f`font-size=\"${ER_FONT.keySize}\" font-weight=\"${ER_FONT.keyWeight}\" fill=\"var(--_text-sec)\">${attr.keys.join(',')}</text>`,\n )\n }\n\n // Type (left-aligned after keys, monospace with syntax highlighting)\n const typeX = boxX + 8 + (keyWidth > 0 ? keyWidth + 6 : 0)\n parts.push(\n f`<text x=\"${typeX}\" y=\"${y}\" class=\"mono\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n f`font-size=\"${ER_FONT.attrSize}\" font-weight=\"${ER_FONT.attrWeight}\">` +\n f`<tspan fill=\"var(--_text-muted)\">${escapeXml(attr.type)}</tspan></text>`,\n )\n\n // Name (right-aligned, monospace with syntax highlighting)\n const nameX = boxX + boxWidth - 8\n parts.push(\n f`<text x=\"${nameX}\" y=\"${y}\" class=\"mono\" text-anchor=\"end\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n f`font-size=\"${ER_FONT.attrSize}\" font-weight=\"${ER_FONT.attrWeight}\">` +\n f`<tspan fill=\"var(--_text-sec)\">${escapeXml(attr.name)}</tspan></text>`,\n )\n\n // Close the group if we opened one\n if (hasComment) {\n parts.push('</g>')\n }\n\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Relationship rendering\n// ============================================================================\n\n/**\n * Render a relationship line with semantic data attributes.\n */\nfunction renderRelationshipLine(rel: PositionedErRelationship): string {\n if (rel.points.length < 2) return ''\n\n const pathData = rel.points.map((p) => f`${p.x},${p.y}`).join(' ')\n const dashArray = !rel.identifying ? ' stroke-dasharray=\"6 4\"' : ''\n\n // Semantic data attributes for relationship inspection\n const labelAttr = rel.label ? f` data-label=\"${escapeAttr(rel.label)}\"` : ''\n const dataAttrs = [\n 'class=\"er-relationship\"',\n f`data-entity1=\"${escapeAttr(rel.entity1)}\"`,\n f`data-entity2=\"${escapeAttr(rel.entity2)}\"`,\n f`data-cardinality1=\"${rel.cardinality1}\"`,\n f`data-cardinality2=\"${rel.cardinality2}\"`,\n f`data-identifying=\"${rel.identifying}\"`,\n ]\n\n return (\n f`<polyline ${dataAttrs.join(' ')}${labelAttr} points=\"${pathData}\" fill=\"none\" stroke=\"var(--_line)\" ` +\n f`stroke-width=\"${STROKE_WIDTHS.connector}\"${dashArray} />`\n )\n}\n\n/** Render a relationship label at the midpoint (supports multi-line) */\nfunction renderRelationshipLabel(\n rel: PositionedErRelationship,\n fontSizes: FontSizes,\n): string {\n if (!rel.label || rel.points.length < 2) return ''\n\n const mid = midpoint(rel.points)\n const metrics = measureMultilineText(\n rel.label,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n\n // Background pill for readability\n const bgW = metrics.width + 8\n const bgH = metrics.height + 6\n\n return (\n f`<rect x=\"${mid.x - bgW / 2}\" y=\"${mid.y - bgH / 2}\" width=\"${bgW}\" height=\"${bgH}\" rx=\"2\" ry=\"2\" ` +\n `fill=\"var(--bg)\" stroke=\"var(--_inner-stroke)\" stroke-width=\"0.5\" />` +\n f`\\n${renderMultilineText(\n rel.label,\n mid.x,\n mid.y,\n fontSizes.edgeLabel,\n f`text-anchor=\"middle\" font-size=\"${fontSizes.edgeLabel}\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n )}`\n )\n}\n\n/**\n * Render crow's foot cardinality markers at both endpoints of a relationship.\n *\n * Crow's foot notation:\n * 'one': ─║─ (single vertical line)\n * 'zero-one': ─o║─ (circle + single line)\n * 'many': ─╢─ (crow's foot + bar)\n * 'zero-many': ─o╣─ (circle + crow's foot)\n */\nfunction renderCardinality(rel: PositionedErRelationship): string {\n if (rel.points.length < 2) return ''\n const parts: string[] = []\n\n // Entity1 side (first point, direction toward second point)\n const p1 = rel.points[0]!\n const p2 = rel.points[1]!\n parts.push(renderCrowsFoot(p1, p2, rel.cardinality1))\n\n // Entity2 side (last point, direction toward second-to-last point)\n const pN = rel.points[rel.points.length - 1]!\n const pN1 = rel.points[rel.points.length - 2]!\n parts.push(renderCrowsFoot(pN, pN1, rel.cardinality2))\n\n return parts.join('\\n')\n}\n\n/**\n * Render a crow's foot marker at a given endpoint.\n * `point` is the endpoint, `toward` gives the direction the line comes from.\n */\nfunction renderCrowsFoot(\n point: { x: number; y: number },\n toward: { x: number; y: number },\n cardinality: Cardinality,\n): string {\n const parts: string[] = []\n const sw = STROKE_WIDTHS.connector + 0.25\n\n // Calculate direction from toward → point (unit vector)\n const dx = point.x - toward.x\n const dy = point.y - toward.y\n const len = Math.sqrt(dx * dx + dy * dy)\n if (len === 0) return ''\n const ux = dx / len\n const uy = dy / len\n\n // Perpendicular direction\n const px = -uy\n const py = ux\n\n // Marker sits 4px from the endpoint, extending 12px back along the edge\n const tipX = point.x - ux * 4\n const tipY = point.y - uy * 4\n const backX = point.x - ux * 16\n const backY = point.y - uy * 16\n\n // Single line: always present for 'one' and part of others\n const hasOneLine = cardinality === 'one' || cardinality === 'zero-one'\n const hasCrowsFoot = cardinality === 'many' || cardinality === 'zero-many'\n const hasCircle = cardinality === 'zero-one' || cardinality === 'zero-many'\n\n // Draw single vertical line (perpendicular to edge) at the tip\n if (hasOneLine) {\n const halfW = 6\n parts.push(\n f`<line x1=\"${tipX + px * halfW}\" y1=\"${tipY + py * halfW}\" ` +\n f`x2=\"${tipX - px * halfW}\" y2=\"${tipY - py * halfW}\" ` +\n f`stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n // Second line slightly back for \"exactly one\" emphasis\n const line2X = tipX - ux * 4\n const line2Y = tipY - uy * 4\n parts.push(\n f`<line x1=\"${line2X + px * halfW}\" y1=\"${line2Y + py * halfW}\" ` +\n f`x2=\"${line2X - px * halfW}\" y2=\"${line2Y - py * halfW}\" ` +\n f`stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n }\n\n // Crow's foot (three lines fanning out from tip)\n if (hasCrowsFoot) {\n const fanW = 7\n // Center line\n const cfTipX = tipX\n const cfTipY = tipY\n // Three lines from tip to back, fanning out\n parts.push(\n // Top fan line\n f`<line x1=\"${cfTipX + px * fanW}\" y1=\"${cfTipY + py * fanW}\" ` +\n f`x2=\"${backX}\" y2=\"${backY}\" ` +\n f`stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n parts.push(\n // Center line\n f`<line x1=\"${cfTipX}\" y1=\"${cfTipY}\" ` +\n f`x2=\"${backX}\" y2=\"${backY}\" ` +\n f`stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n parts.push(\n // Bottom fan line\n f`<line x1=\"${cfTipX - px * fanW}\" y1=\"${cfTipY - py * fanW}\" ` +\n f`x2=\"${backX}\" y2=\"${backY}\" ` +\n f`stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n }\n\n // Bar across the foot's convergence point: distinguishes one-or-many\n // from a bare crow's foot (#1233)\n if (cardinality === 'many') {\n const barX = point.x - ux * 20\n const barY = point.y - uy * 20\n const halfW = 6\n parts.push(\n f`<line x1=\"${barX + px * halfW}\" y1=\"${barY + py * halfW}\" ` +\n f`x2=\"${barX - px * halfW}\" y2=\"${barY - py * halfW}\" ` +\n f`stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n }\n\n // Circle (for zero variants)\n if (hasCircle) {\n const circleOffset = hasCrowsFoot ? 20 : 12\n const circleX = point.x - ux * circleOffset\n const circleY = point.y - uy * circleOffset\n parts.push(\n f`<circle cx=\"${circleX}\" cy=\"${circleY}\" r=\"4\" ` +\n f`fill=\"var(--bg)\" stroke=\"var(--_line)\" stroke-width=\"${sw}\" />`,\n )\n }\n\n return parts.join('\\n')\n}\n\n/** Compute the arc-length midpoint of a polyline path.\n * Walks along each segment, finds the point at exactly 50% of total path length.\n * This ensures the label sits ON the path even for orthogonal routes with bends,\n * unlike the naive first/last geometric center which floats in space for L/Z shapes. */\nfunction midpoint(points: Array<{ x: number; y: number }>): {\n x: number\n y: number\n} {\n if (points.length === 0) return { x: 0, y: 0 }\n if (points.length === 1) return points[0]!\n\n // Compute total path length\n let totalLen = 0\n for (let i = 1; i < points.length; i++) {\n const dx = points[i]!.x - points[i - 1]!.x\n const dy = points[i]!.y - points[i - 1]!.y\n totalLen += Math.sqrt(dx * dx + dy * dy)\n }\n\n if (totalLen === 0) return points[0]!\n\n // Walk to 50% of total length, interpolating within the segment that crosses the halfway mark\n const halfLen = totalLen / 2\n let walked = 0\n for (let i = 1; i < points.length; i++) {\n const dx = points[i]!.x - points[i - 1]!.x\n const dy = points[i]!.y - points[i - 1]!.y\n const segLen = Math.sqrt(dx * dx + dy * dy)\n if (walked + segLen >= halfLen) {\n const t = segLen > 0 ? (halfLen - walked) / segLen : 0\n return {\n x: points[i - 1]!.x + dx * t,\n y: points[i - 1]!.y + dy * t,\n }\n }\n walked += segLen\n }\n\n return points[points.length - 1]!\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n// Use shared escapeXml from multiline-utils\nconst escapeXml = escapeXmlUtil\n","import type {\n SequenceDiagram,\n PositionedSequenceDiagram,\n PositionedActor,\n Lifeline,\n PositionedMessage,\n Activation,\n PositionedBlock,\n PositionedNote,\n PositionedParticipantBox,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { RenderOptions, SequenceRenderOptions } from '@zombie-mermaid/core'\nimport { estimateTextWidth, FONT_WEIGHTS, resolveFontSizes } from '../styles.ts'\n\n// ============================================================================\n// Sequence diagram layout engine\n//\n// Custom timeline-based layout (no ELK — sequence diagrams aren't graphs).\n//\n// Layout strategy:\n// 1. Space actors horizontally based on label widths + min gap\n// 2. Stack messages vertically in chronological order\n// 3. Track activation boxes via a stack\n// 4. Position blocks (loop/alt/opt) as background rectangles\n// 5. Position notes next to their target actors\n// ============================================================================\n\n/** Layout constants specific to sequence diagrams */\nconst SEQ = {\n /** Padding around the entire diagram */\n padding: 30,\n /** Minimum gap between actor centers */\n actorGap: 140,\n /** Actor box height */\n actorHeight: 40,\n /** Horizontal padding inside actor boxes */\n actorPadX: 16,\n /** Vertical space between actor boxes and first message */\n headerGap: 20,\n /** Vertical space per message row */\n messageRowHeight: 40,\n /** Extra vertical space for self-messages (they loop back) */\n selfMessageHeight: 30,\n /** Activation box width (narrow rectangle on lifeline) */\n activationWidth: 10,\n /** Block padding (loop/alt borders) */\n blockPadX: 10,\n blockPadTop: 40,\n blockPadBottom: 8,\n /** Extra vertical space before the first message in a block (room for the header label) */\n blockHeaderExtra: 28,\n /** Extra vertical space before a message at a divider boundary (room for else/and label) */\n dividerExtra: 24,\n /** Note dimensions */\n noteWidth: 60,\n notePadX: 12,\n notePadY: 6,\n noteGap: 10,\n /** Gap between a message arrow and a note positioned directly after it */\n noteOffsetAfterMessage: 8,\n /** Gap between consecutively stacked notes */\n noteStackGap: 4,\n /**\n * `box … end` group padding: horizontal clearance beyond the outermost\n * member's box, vertical clearance above the actor boxes (under the label\n * band) and below the lifeline bottoms.\n */\n boxPad: 10,\n} as const\n\n/**\n * Height of the label band at the top of a `box … end` group, in px — the\n * label's font size plus vertical breathing room. Shared with the renderer\n * so the label lands centred in the band the layout reserved for it.\n */\nexport function boxLabelHeight(labelFontSize: number): number {\n return labelFontSize + 8\n}\n\n/** Resolved sequence-layout config — same shape as {@link SEQ} but mutable numbers. */\ntype SeqConfig = { [K in keyof typeof SEQ]: number }\n\n/**\n * Merge user-provided sequence-layout overrides over the {@link SEQ} defaults.\n * Any field left unspecified (or `undefined`) falls back to its default.\n */\nfunction resolveSeqOptions(overrides?: RenderOptions['sequence']): SeqConfig {\n return {\n ...SEQ,\n actorHeight: overrides?.actorHeight ?? SEQ.actorHeight,\n headerGap: overrides?.headerGap ?? SEQ.headerGap,\n messageRowHeight: overrides?.messageRowHeight ?? SEQ.messageRowHeight,\n noteOffsetAfterMessage:\n overrides?.noteOffsetAfterMessage ?? SEQ.noteOffsetAfterMessage,\n noteStackGap: overrides?.noteStackGap ?? SEQ.noteStackGap,\n }\n}\n\n/**\n * Lay out a parsed sequence diagram.\n * Returns a fully positioned diagram ready for SVG rendering.\n */\nexport function layoutSequenceDiagram(\n diagram: SequenceDiagram,\n options: SequenceRenderOptions = {},\n): PositionedSequenceDiagram {\n if (diagram.actors.length === 0) {\n return {\n width: 0,\n height: 0,\n actors: [],\n lifelines: [],\n messages: [],\n activations: [],\n blocks: [],\n notes: [],\n boxes: [],\n }\n }\n\n const seq = resolveSeqOptions(options.sequence)\n const fontSizes = resolveFontSizes(options.fontSizes)\n\n // 1. Calculate actor widths and assign horizontal positions (center X)\n const actorWidths = diagram.actors.map((a) => {\n const textW = estimateTextWidth(\n a.label,\n fontSizes.nodeLabel,\n FONT_WEIGHTS.nodeLabel,\n )\n return Math.max(textW + seq.actorPadX * 2, 80)\n })\n\n // Build actor center X positions with minimum gap\n const actorCenterX: number[] = []\n let currentX = seq.padding + actorWidths[0]! / 2\n for (let i = 0; i < diagram.actors.length; i++) {\n if (i > 0) {\n const minGap = Math.max(\n seq.actorGap,\n (actorWidths[i - 1]! + actorWidths[i]!) / 2 + 40,\n )\n currentX += minGap\n }\n actorCenterX.push(currentX)\n }\n\n // Build actor ID → index lookup\n const actorIndex = new Map<string, number>()\n for (let i = 0; i < diagram.actors.length; i++) {\n actorIndex.set(diagram.actors[i]!.id, i)\n }\n\n // 2. Position actors at the top. A `box … end` group draws a full-height\n // background starting at the top padding, so when any (non-empty) box\n // exists the actor row moves down to leave room for the box's top\n // padding and — if any box has a label — its label band. Mermaid does\n // the same (`bumpVerticalPos(boxes[0].textMaxHeight)`).\n const renderedBoxes = diagram.boxes.filter((b) => b.actorIds.length > 0)\n const anyBoxLabel = renderedBoxes.some((b) => b.label !== '')\n const boxTopInset =\n renderedBoxes.length === 0\n ? 0\n : seq.boxPad + (anyBoxLabel ? boxLabelHeight(fontSizes.edgeLabel) : 0)\n const actorY = seq.padding + boxTopInset\n const actors: PositionedActor[] = diagram.actors.map((a, i) => ({\n id: a.id,\n label: a.label,\n type: a.type,\n x: actorCenterX[i]!,\n y: actorY,\n width: actorWidths[i]!,\n height: seq.actorHeight,\n }))\n\n // Box backgrounds span from the outermost member's left edge to the\n // outermost member's right edge (plus padding) — any participant declared\n // between two members sits visually inside, as in Mermaid. Heights are\n // filled in once the diagram bottom is known (step 8 below).\n const boxes: PositionedParticipantBox[] = renderedBoxes.map((box) => {\n const idxs = box.actorIds.map((id) => actorIndex.get(id) ?? 0)\n const lo = Math.min(...idxs)\n const hi = Math.max(...idxs)\n const left = actorCenterX[lo]! - actorWidths[lo]! / 2 - seq.boxPad\n const right = actorCenterX[hi]! + actorWidths[hi]! / 2 + seq.boxPad\n const positioned: PositionedParticipantBox = {\n label: box.label,\n x: left,\n y: seq.padding,\n width: right - left,\n height: 0,\n }\n if (box.color !== undefined) positioned.color = box.color\n return positioned\n })\n\n // 3. Stack messages vertically\n let messageY = actorY + seq.actorHeight + seq.headerGap\n const messages: PositionedMessage[] = []\n\n // Pre-scan blocks to determine which message indices need extra vertical\n // space for block headers (e.g. \"alt [Valid credentials]\") or divider\n // labels (e.g. \"[else Invalid]\"). Without this, messages inside blocks\n // overlap with the header/divider text that sits above them.\n const extraSpaceBefore = new Map<number, number>()\n for (const block of diagram.blocks) {\n // First message in the block needs room for the block header label\n const prev = extraSpaceBefore.get(block.startIndex) ?? 0\n extraSpaceBefore.set(block.startIndex, Math.max(prev, seq.blockHeaderExtra))\n\n // Each divider (else/and) needs room for the divider label\n for (const div of block.dividers) {\n const prevDiv = extraSpaceBefore.get(div.index) ?? 0\n extraSpaceBefore.set(div.index, Math.max(prevDiv, seq.dividerExtra))\n }\n }\n\n // Pre-group notes by the message index they follow, so we can position\n // them inline during the message stacking loop (avoids overlap bugs).\n const notesByAfterIndex = new Map<number, typeof diagram.notes>()\n for (const note of diagram.notes) {\n const list = notesByAfterIndex.get(note.afterIndex) ?? []\n list.push(note)\n notesByAfterIndex.set(note.afterIndex, list)\n }\n const positionedNotes: PositionedNote[] = []\n\n // Handle notes that appear before the first message (afterIndex === -1)\n const notesBeforeFirstMsg = notesByAfterIndex.get(-1)\n if (notesBeforeFirstMsg && notesBeforeFirstMsg.length > 0) {\n let noteY = messageY\n for (const note of notesBeforeFirstMsg) {\n const noteW = Math.max(\n seq.noteWidth,\n estimateTextWidth(\n note.text,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n ) +\n seq.notePadX * 2,\n )\n const noteH = fontSizes.edgeLabel + seq.notePadY * 2\n\n // X positioning based on actor position and note type\n const firstActorIdx = actorIndex.get(note.actorIds[0] ?? '') ?? 0\n let noteX: number\n if (note.position === 'left') {\n noteX =\n actorCenterX[firstActorIdx]! -\n actorWidths[firstActorIdx]! / 2 -\n noteW -\n seq.noteGap\n } else if (note.position === 'right') {\n noteX =\n actorCenterX[firstActorIdx]! +\n actorWidths[firstActorIdx]! / 2 +\n seq.noteGap\n } else {\n // over — center between first and last actor\n if (note.actorIds.length > 1) {\n const lastActorIdx =\n actorIndex.get(note.actorIds[note.actorIds.length - 1] ?? '') ??\n firstActorIdx\n noteX =\n (actorCenterX[firstActorIdx]! + actorCenterX[lastActorIdx]!) / 2 -\n noteW / 2\n } else {\n noteX = actorCenterX[firstActorIdx]! - noteW / 2\n }\n }\n\n positionedNotes.push({\n text: note.text,\n x: noteX,\n y: noteY,\n width: noteW,\n height: noteH,\n position: note.position,\n actors: note.actorIds,\n })\n\n noteY += noteH + seq.noteStackGap\n }\n\n messageY = Math.max(messageY, noteY + seq.messageRowHeight / 2)\n }\n\n // Track activation stack per actor: array of { startY, depth } objects\n // Depth is used to offset nested activations horizontally for visual clarity\n const activationStacks = new Map<\n string,\n { startY: number; depth: number }[]\n >()\n const activations: Activation[] = []\n const nestingOffset = 4 // Horizontal offset per nesting level\n\n /** Open an activation bar on `actorId` at row `y` (stacks for nesting). */\n function startActivation(actorId: string, y: number): void {\n const stack = activationStacks.get(actorId) ?? []\n activationStacks.set(actorId, stack)\n const depth = stack.length // Current depth before pushing\n stack.push({ startY: y, depth })\n }\n\n /**\n * Close the innermost open activation on `actorId` at row `y`. A\n * deactivate with nothing open is ignored rather than fatal (Mermaid\n * itself errors here; this renderer has always been lenient about it).\n */\n function endActivation(actorId: string, y: number): void {\n const stack = activationStacks.get(actorId)\n if (!stack || stack.length === 0) return\n const { startY, depth } = stack.pop()!\n const idx = actorIndex.get(actorId) ?? 0\n // Offset nested activations to the right for visual distinction\n const xOffset = depth * nestingOffset\n activations.push({\n actorId,\n x: actorCenterX[idx]! - seq.activationWidth / 2 + xOffset,\n topY: startY,\n bottomY: y,\n width: seq.activationWidth,\n })\n }\n\n /**\n * Standalone `activate`/`deactivate` statements, grouped by the message\n * they follow. Applied at that message's row — the same row the `+`/`-`\n * shorthand on that message would use — through the same two helpers, so\n * the two spellings can't drift apart.\n */\n const activationEventsByAfterIndex = new Map<\n number,\n typeof diagram.activations\n >()\n for (const event of diagram.activations) {\n const list = activationEventsByAfterIndex.get(event.afterIndex) ?? []\n list.push(event)\n activationEventsByAfterIndex.set(event.afterIndex, list)\n }\n function applyActivationEvents(afterIndex: number, y: number): void {\n for (const event of activationEventsByAfterIndex.get(afterIndex) ?? []) {\n if (event.kind === 'start') startActivation(event.actorId, y)\n else endActivation(event.actorId, y)\n }\n }\n\n // Events before the first message open at the top of the message area.\n applyActivationEvents(-1, messageY)\n\n for (let msgIdx = 0; msgIdx < diagram.messages.length; msgIdx++) {\n const msg = diagram.messages[msgIdx]!\n const fromIdx = actorIndex.get(msg.from) ?? 0\n const toIdx = actorIndex.get(msg.to) ?? 0\n const isSelf = msg.from === msg.to\n\n // Add extra vertical space if this message sits below a block header or divider\n const extra = extraSpaceBefore.get(msgIdx) ?? 0\n if (extra > 0) messageY += extra\n\n const x1 = actorCenterX[fromIdx]!\n let x2 = actorCenterX[toIdx]!\n\n // A `create participant` message ends at the near edge of the box it\n // creates rather than at the lifeline centre — the box sits on this row\n // (see the created-actor placement after this loop), so an arrow to the\n // centre would run underneath it. Mermaid's renderer makes the same\n // adjustment (`receiverAdjustment(actor, width / 2)`).\n const createsRecipient =\n !isSelf && diagram.actors[toIdx]!.createdAt === msgIdx\n if (createsRecipient) {\n const halfW = actorWidths[toIdx]! / 2\n x2 += x1 < x2 ? -halfW : halfW\n }\n\n messages.push({\n from: msg.from,\n to: msg.to,\n label: msg.label,\n lineStyle: msg.lineStyle,\n arrowHead: msg.arrowHead,\n x1,\n x2,\n y: messageY,\n isSelf,\n bidirectional: msg.bidirectional ?? false,\n seqNumber: msg.seqNumber,\n })\n\n // Handle activation - track nesting depth for visual offset. The `+`\n // shorthand activates the recipient, `-` deactivates the sender (see\n // parser.ts); standalone statements following this message apply at\n // the same row, after the shorthand, in source order.\n if (msg.activate) startActivation(msg.to, messageY)\n if (msg.deactivate) endActivation(msg.from, messageY)\n applyActivationEvents(msgIdx, messageY)\n\n // Advance messageY past the message itself\n messageY += isSelf\n ? seq.selfMessageHeight + seq.messageRowHeight\n : seq.messageRowHeight\n // The created box is centred on this row, so its lower half hangs\n // below the arrow — give the next row room to clear it (Mermaid bumps\n // by the same half-height after a creating message).\n if (createsRecipient) messageY += seq.actorHeight / 2\n\n // Position notes that appear after this message.\n // Notes start below the self-message loop (if self) or below the arrow,\n // and consecutive notes stack vertically. If notes extend beyond the\n // normal message advance, push messageY further so subsequent messages\n // don't overlap.\n const notesForMsg = notesByAfterIndex.get(msgIdx)\n if (notesForMsg && notesForMsg.length > 0) {\n // Self-message loops extend selfMessageHeight below msg.y;\n // normal arrows sit at msg.y with no extension below.\n const selfLoopExtra = isSelf ? seq.selfMessageHeight : 0\n let noteY =\n messages[msgIdx]!.y + selfLoopExtra + seq.noteOffsetAfterMessage\n\n for (const note of notesForMsg) {\n const noteW = Math.max(\n seq.noteWidth,\n estimateTextWidth(\n note.text,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n ) +\n seq.notePadX * 2,\n )\n const noteH = fontSizes.edgeLabel + seq.notePadY * 2\n\n // X positioning based on actor position and note type\n const firstActorIdx = actorIndex.get(note.actorIds[0] ?? '') ?? 0\n let noteX: number\n if (note.position === 'left') {\n noteX =\n actorCenterX[firstActorIdx]! -\n actorWidths[firstActorIdx]! / 2 -\n noteW -\n seq.noteGap\n } else if (note.position === 'right') {\n noteX =\n actorCenterX[firstActorIdx]! +\n actorWidths[firstActorIdx]! / 2 +\n seq.noteGap\n } else {\n // over — center between first and last actor\n if (note.actorIds.length > 1) {\n const lastActorIdx =\n actorIndex.get(note.actorIds[note.actorIds.length - 1] ?? '') ??\n firstActorIdx\n noteX =\n (actorCenterX[firstActorIdx]! + actorCenterX[lastActorIdx]!) / 2 -\n noteW / 2\n } else {\n noteX = actorCenterX[firstActorIdx]! - noteW / 2\n }\n }\n\n positionedNotes.push({\n text: note.text,\n x: noteX,\n y: noteY,\n width: noteW,\n height: noteH,\n position: note.position,\n actors: note.actorIds,\n })\n\n noteY += noteH + seq.noteStackGap // Stack next note below with gap\n }\n\n // Push messageY forward if notes extended beyond the normal advance.\n // Add half a row height so the next message's label (rendered at msg.y - 6)\n // has clearance from the last note's bottom edge.\n messageY = Math.max(messageY, noteY + seq.messageRowHeight / 2)\n }\n }\n\n // Close any unclosed activations (preserving depth for offset)\n for (const [actorId, stack] of activationStacks) {\n for (const { startY, depth } of stack) {\n const idx = actorIndex.get(actorId) ?? 0\n const xOffset = depth * nestingOffset\n activations.push({\n actorId,\n x: actorCenterX[idx]! - seq.activationWidth / 2 + xOffset,\n topY: startY,\n bottomY: messageY - seq.messageRowHeight / 2,\n width: seq.activationWidth,\n })\n }\n }\n\n // 4. Position blocks (loop/alt/opt)\n const blocks: PositionedBlock[] = diagram.blocks.map((block) => {\n // Block spans from the Y of startIndex to endIndex messages\n const startMsg = messages[block.startIndex]\n const endMsg = messages[block.endIndex]\n const blockTop = (startMsg?.y ?? messageY) - seq.blockPadTop\n const blockBottom = (endMsg?.y ?? messageY) + seq.blockPadBottom + 12\n\n // Block width spans all actors involved in its messages\n const involvedActors = new Set<number>()\n for (let mi = block.startIndex; mi <= block.endIndex; mi++) {\n const m = diagram.messages[mi]\n if (m) {\n involvedActors.add(actorIndex.get(m.from) ?? 0)\n involvedActors.add(actorIndex.get(m.to) ?? 0)\n }\n }\n // Fallback: span all actors if none involved\n if (involvedActors.size === 0) {\n for (let ai = 0; ai < diagram.actors.length; ai++) involvedActors.add(ai)\n }\n const minIdx = Math.min(...involvedActors)\n const maxIdx = Math.max(...involvedActors)\n const blockLeft =\n actorCenterX[minIdx]! - actorWidths[minIdx]! / 2 - seq.blockPadX\n const blockRight =\n actorCenterX[maxIdx]! + actorWidths[maxIdx]! / 2 + seq.blockPadX\n\n // Position dividers — offset from message Y so the divider label text\n // (rendered at divider.y + 14 in the renderer) clears the message label\n // (rendered at msg.y - 6).\n //\n // Default offset 28 gives ~8px baseline clearance, which is sufficient\n // when the divider label (left-aligned at block edge) and message label\n // (centered between actors) don't share horizontal space. When they DO\n // overlap horizontally (e.g. long divider labels like \"[Account locked]\"\n // next to centered message labels like \"403 Forbidden\"), we increase the\n // offset to 36 so text bounding boxes have ~5px visual clearance.\n const dividers = block.dividers.map((d) => {\n const msg = messages[d.index]\n const msgY = msg?.y ?? messageY\n let offset = 28\n\n // Dynamic overlap detection: increase offset when the divider label\n // and message label occupy the same horizontal region, which would\n // cause vertical text overlap at the default 8px baseline gap.\n if (d.label && msg?.label) {\n const divLabelText = `[${d.label}]`\n const divLabelW = estimateTextWidth(\n divLabelText,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n const divLabelLeft = blockLeft + 8\n const divLabelRight = divLabelLeft + divLabelW\n\n const msgLabelW = estimateTextWidth(\n msg.label,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n // Self-messages render labels at x1 + 36 (left-aligned); normal\n // messages center the label between the two actor lifelines.\n const msgLabelLeft = msg.isSelf\n ? msg.x1 + 36\n : (msg.x1 + msg.x2) / 2 - msgLabelW / 2\n const msgLabelRight = msgLabelLeft + msgLabelW\n\n if (divLabelRight > msgLabelLeft && divLabelLeft < msgLabelRight) {\n offset = 36\n }\n }\n\n return { y: msgY - offset, label: d.label }\n })\n\n return {\n type: block.type,\n label: block.label,\n x: blockLeft,\n y: blockTop,\n width: blockRight - blockLeft,\n height: blockBottom - blockTop,\n dividers,\n }\n })\n\n // 5. Notes — already positioned inline during the message stacking loop\n // (step 3) to properly account for self-message loops and vertical stacking.\n const notes = positionedNotes\n\n // 6. Bounding-box post-processing\n //\n // Notes positioned \"left of\" the first actor or \"right of\" the last actor\n // can extend beyond the actor-based viewport. Compute the true bounding box\n // across all positioned elements, then shift everything right if anything\n // extends left of the desired padding margin and expand the width to fit.\n const diagramBottom = messageY + seq.padding\n\n // Find global X extents across actors, blocks, notes, and message labels\n let globalMinX: number = seq.padding // actors already start at seq.padding\n let globalMaxX = 0\n for (const a of actors) {\n globalMinX = Math.min(globalMinX, a.x - a.width / 2)\n globalMaxX = Math.max(globalMaxX, a.x + a.width / 2)\n }\n for (const b of blocks) {\n globalMinX = Math.min(globalMinX, b.x)\n globalMaxX = Math.max(globalMaxX, b.x + b.width)\n }\n for (const n of notes) {\n globalMinX = Math.min(globalMinX, n.x)\n globalMaxX = Math.max(globalMaxX, n.x + n.width)\n }\n for (const b of boxes) {\n globalMinX = Math.min(globalMinX, b.x)\n globalMaxX = Math.max(globalMaxX, b.x + b.width)\n }\n // Include self-message labels in bounding box — they extend to the right of the actor\n // and could be clipped if not accounted for in the SVG width\n for (const m of messages) {\n if (m.isSelf && m.label) {\n const loopW = 30 // matches renderer loopW\n const labelPadding = 8\n const labelLeft = m.x1 + loopW + labelPadding\n const labelWidth = estimateTextWidth(\n m.label,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.edgeLabel,\n )\n globalMaxX = Math.max(globalMaxX, labelLeft + labelWidth + 8) // +8 for safety margin\n }\n }\n\n // If elements extend left of the desired padding, shift everything right\n const shiftX = globalMinX < seq.padding ? seq.padding - globalMinX : 0\n if (shiftX > 0) {\n for (const a of actors) a.x += shiftX\n for (const m of messages) {\n m.x1 += shiftX\n m.x2 += shiftX\n }\n for (const act of activations) act.x += shiftX\n for (const b of blocks) {\n b.x += shiftX\n }\n for (const n of notes) n.x += shiftX\n for (const b of boxes) b.x += shiftX\n // Also shift actor center X array (used for lifelines below)\n for (let i = 0; i < actorCenterX.length; i++) actorCenterX[i]! += shiftX\n }\n\n // 7. Participant lifecycle: a created participant's box moves from the\n // header down to its creating message's row, centred on the arrow\n // (Mermaid: `actor.starty = lineStartY - actor.height / 2`), and a\n // destroyed participant's lifeline stops at its destroying message.\n // Both indices come from the parser, which already verified the\n // message exists and involves the actor — the `?? messageY` fallbacks\n // only guard the type, not a reachable state.\n for (let i = 0; i < diagram.actors.length; i++) {\n const createdAt = diagram.actors[i]!.createdAt\n if (createdAt !== undefined) {\n actors[i]!.y = (messages[createdAt]?.y ?? messageY) - seq.actorHeight / 2\n }\n }\n\n // 8. Calculate final lifelines (after shift so X positions are correct)\n const lifelines: Lifeline[] = diagram.actors.map((a, i) => {\n const destroyedAt = a.destroyedAt\n const lifeline: Lifeline = {\n actorId: a.id,\n x: actorCenterX[i]!,\n topY: actors[i]!.y + seq.actorHeight,\n bottomY:\n destroyedAt === undefined\n ? diagramBottom - seq.padding\n : (messages[destroyedAt]?.y ?? messageY),\n }\n if (destroyedAt !== undefined) lifeline.destroyed = true\n return lifeline\n })\n\n // Box backgrounds run from the top padding to just under the lifeline\n // bottoms (boxPad < padding, so this stays inside the diagram height).\n for (const b of boxes) {\n b.height = diagramBottom - seq.padding + seq.boxPad - b.y\n }\n\n // 9. Calculate diagram dimensions from the bounding box\n const diagramWidth = globalMaxX + shiftX + seq.padding\n const diagramHeight = diagramBottom\n\n return {\n width: Math.max(diagramWidth, 200),\n height: Math.max(diagramHeight, 100),\n actors,\n lifelines,\n messages,\n activations,\n blocks,\n notes,\n boxes,\n }\n}\n","import type {\n PositionedSequenceDiagram,\n PositionedActor,\n Lifeline,\n PositionedMessage,\n Activation,\n PositionedBlock,\n PositionedNote,\n PositionedParticipantBox,\n} from '@zombie-mermaid/mermaid-parser'\nimport { boxLabelHeight } from './layout.ts'\nimport type { DiagramColors, SvgEmitOptions } from '@zombie-mermaid/core'\nimport {\n svgOpenTag,\n buildStyleBlock,\n renderMultilineText,\n escapeAttr,\n f,\n} from '@zombie-mermaid/core'\nimport { withDataSrc } from '../renderer.ts'\nimport {\n FONT_SIZES,\n FONT_WEIGHTS,\n STROKE_WIDTHS,\n ARROW_HEAD,\n estimateTextWidth,\n} from '../styles.ts'\nimport type { FontSizes } from '../styles.ts'\n\n// ============================================================================\n// Sequence diagram SVG renderer\n//\n// Renders a positioned sequence diagram to SVG string.\n// All colors use CSS custom properties (var(--_xxx)) from the theme system.\n//\n// Render order (back to front):\n// 0. Participant-group backgrounds (box … end)\n// 1. Block backgrounds (loop/alt/opt)\n// 2. Lifelines (dashed vertical lines)\n// 3. Activation boxes\n// 4. Messages (arrows with labels)\n// 5. Notes\n// 6. Actor boxes (at top)\n// ============================================================================\n\n/**\n * Render a positioned sequence diagram as an SVG string.\n *\n * @param colors - DiagramColors with bg/fg and optional enrichment variables.\n * @param transparent - If true, renders with transparent background.\n * @param embedSource - Original diagram source to stamp onto the root `<svg>`\n * as `data-src` (from `options.embedSource`). Omitted\n * when the option is off.\n * @param title - Accessible name (from `options.title`). See svgOpenTag() in\n * packages/core/src/theme.ts.\n * @param decorative - Marks the SVG decorative (from `options.decorative`).\n * @param emit - Strict-CSP controls (from `options.nonce` /\n * `options.styleAttribute`, see #216). Default: no nonce,\n * root `style` attribute on.\n */\nexport function renderSequenceSvg(\n diagram: PositionedSequenceDiagram,\n colors: DiagramColors,\n font: string = 'Inter',\n transparent: boolean = false,\n fontSizes: FontSizes = FONT_SIZES,\n embedSource?: string,\n title?: string,\n decorative?: boolean,\n emit: SvgEmitOptions = {},\n): string {\n const parts: string[] = []\n\n // SVG root with CSS variables + style block + defs\n parts.push(\n withDataSrc(\n svgOpenTag(\n diagram.width,\n diagram.height,\n colors,\n transparent,\n title,\n decorative,\n undefined,\n emit.styleAttribute,\n ),\n embedSource,\n ),\n )\n parts.push(buildStyleBlock(font, false, emit.nonce))\n parts.push('<defs>')\n\n // Arrow marker definitions\n parts.push(arrowMarkerDefs())\n parts.push('</defs>')\n\n // 0. Participant-group backgrounds (box … end), behind everything\n for (const box of diagram.boxes) {\n parts.push(renderParticipantBox(box, fontSizes))\n }\n\n // 1. Block backgrounds (loop/alt/opt rectangles)\n for (const block of diagram.blocks) {\n parts.push(renderBlock(block, fontSizes))\n }\n\n // 2. Lifelines (dashed vertical lines from actor to bottom)\n for (const lifeline of diagram.lifelines) {\n parts.push(renderLifeline(lifeline))\n }\n\n // 3. Activation boxes\n for (const activation of diagram.activations) {\n parts.push(renderActivation(activation))\n }\n\n // 4. Messages (horizontal arrows with labels)\n for (const message of diagram.messages) {\n parts.push(renderMessage(message, fontSizes))\n }\n\n // 5. Notes\n for (const note of diagram.notes) {\n parts.push(renderNote(note, fontSizes))\n }\n\n // 6. Actor boxes at top (rendered last so they're on top)\n for (const actor of diagram.actors) {\n parts.push(renderActor(actor, fontSizes))\n }\n\n parts.push('</svg>')\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Arrow marker definitions\n// ============================================================================\n\nfunction arrowMarkerDefs(): string {\n const w = ARROW_HEAD.width\n const h = ARROW_HEAD.height\n return (\n f` <marker id=\"seq-arrow\" markerWidth=\"${w}\" markerHeight=\"${h}\" refX=\"${w}\" refY=\"${h / 2}\" orient=\"auto-start-reverse\">` +\n f`\\n <polygon points=\"0 0, ${w} ${h / 2}, 0 ${h}\" fill=\"var(--_arrow)\" />` +\n `\\n </marker>` +\n // Open arrow head (just lines, no fill)\n f`\\n <marker id=\"seq-arrow-open\" markerWidth=\"${w}\" markerHeight=\"${h}\" refX=\"${w}\" refY=\"${h / 2}\" orient=\"auto-start-reverse\">` +\n f`\\n <polyline points=\"0 0, ${w} ${h / 2}, 0 ${h}\" fill=\"none\" stroke=\"var(--_arrow)\" stroke-width=\"1\" />` +\n `\\n </marker>`\n )\n}\n\n// ============================================================================\n// Component renderers\n// ============================================================================\n\n/**\n * Render an actor box (participant = rectangle, actor = stick figure).\n * Wrapped in <g class=\"actor\"> with semantic data attributes.\n */\nfunction renderActor(actor: PositionedActor, fontSizes: FontSizes): string {\n const { id, x, y, width, height, label, type } = actor\n const parts: string[] = []\n\n // Semantic wrapper with actor metadata\n parts.push(\n f`<g class=\"actor\" data-id=\"${escapeAttr(id)}\" data-label=\"${escapeAttr(label)}\" data-type=\"${type}\">`,\n )\n\n if (type === 'actor') {\n // Circle-person icon: outer circle + head circle + shoulders arc.\n // Defined in a 24×24 coordinate space, scaled to 90% of the actor box height\n // and centered both horizontally and vertically within the box.\n // Stroke width is inverse-scaled so the visual thickness matches STROKE_WIDTHS.outerBox.\n const s = (height / 24) * 0.9\n const tx = x - 12 * s // center icon horizontally on actor.x\n const ty = y + (height - 24 * s) / 2 // center icon vertically in actor box\n const sw = STROKE_WIDTHS.outerBox / s // compensate for scale transform\n const iconStroke = 'var(--_line)' // use line color for actor icon strokes\n\n parts.push(\n f` <g transform=\"translate(${tx},${ty}) scale(${s})\">` +\n // Outer circle\n f`\\n <path d=\"M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z\" fill=\"none\" stroke=\"${iconStroke}\" stroke-width=\"${sw}\" />` +\n // Head\n f`\\n <path d=\"M15 10C15 11.6569 13.6569 13 12 13C10.3431 13 9 11.6569 9 10C9 8.34315 10.3431 7 12 7C13.6569 7 15 8.34315 15 10Z\" fill=\"none\" stroke=\"${iconStroke}\" stroke-width=\"${sw}\" />` +\n // Shoulders\n f`\\n <path d=\"M5.62842 18.3563C7.08963 17.0398 9.39997 16 12 16C14.6 16 16.9104 17.0398 18.3716 18.3563\" fill=\"none\" stroke=\"${iconStroke}\" stroke-width=\"${sw}\" />` +\n `\\n </g>`,\n )\n // Label below the icon (supports multi-line)\n parts.push(\n ' ' +\n renderMultilineText(\n label,\n x,\n y + height + 14,\n fontSizes.nodeLabel,\n f`font-size=\"${fontSizes.nodeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.nodeLabel}\" fill=\"var(--_text)\"`,\n ),\n )\n } else {\n // Participant: rectangle box with label (supports multi-line)\n const boxX = x - width / 2\n parts.push(\n f` <rect x=\"${boxX}\" y=\"${y}\" width=\"${width}\" height=\"${height}\" rx=\"4\" ry=\"4\" ` +\n f`fill=\"var(--_node-fill)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n parts.push(\n ' ' +\n renderMultilineText(\n label,\n x,\n y + height / 2,\n fontSizes.nodeLabel,\n f`font-size=\"${fontSizes.nodeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.nodeLabel}\" fill=\"var(--_text)\"`,\n ),\n )\n }\n\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n/**\n * Render a lifeline (dashed vertical line from actor to bottom).\n * Includes data-actor to link to its actor.\n */\nfunction renderLifeline(lifeline: Lifeline): string {\n const line =\n f`<line class=\"lifeline\" data-actor=\"${escapeAttr(lifeline.actorId)}\" ` +\n f`x1=\"${lifeline.x}\" y1=\"${lifeline.topY}\" x2=\"${lifeline.x}\" y2=\"${lifeline.bottomY}\" ` +\n `stroke=\"var(--_line)\" stroke-width=\"0.75\" stroke-dasharray=\"6 4\" />`\n if (!lifeline.destroyed) return line\n // `destroy X`: the lifeline ends at the destroying message's row, marked\n // with a cross centred on it — the same glyph Mermaid uses. Drawn in the\n // lifeline pass (before messages), so the destroying arrow lands on top.\n const { x, bottomY: y } = lifeline\n const r = DESTROY_CROSS_HALF\n return (\n line +\n f`\\n<path class=\"destroy\" data-actor=\"${escapeAttr(lifeline.actorId)}\" ` +\n f`d=\"M${x - r} ${y - r} L${x + r} ${y + r} M${x + r} ${y - r} L${x - r} ${y + r}\" ` +\n f`fill=\"none\" stroke=\"var(--_line)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" stroke-linecap=\"round\" />`\n )\n}\n\n/** Half-size of the `destroy` cross at the end of a lifeline, in px. */\nconst DESTROY_CROSS_HALF = 8\n\n/**\n * Render an activation box (narrow filled rectangle on lifeline).\n * Includes data-actor to link to its actor.\n */\nfunction renderActivation(activation: Activation): string {\n return (\n f`<rect class=\"activation\" data-actor=\"${escapeAttr(activation.actorId)}\" ` +\n f`x=\"${activation.x}\" y=\"${activation.topY}\" width=\"${activation.width}\" height=\"${activation.bottomY - activation.topY}\" ` +\n f`fill=\"var(--_node-fill)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />`\n )\n}\n\n/**\n * Render a message arrow with label.\n * Wrapped in <g class=\"message\"> with semantic data attributes.\n */\nfunction renderMessage(msg: PositionedMessage, fontSizes: FontSizes): string {\n const parts: string[] = []\n const dashArray = msg.lineStyle === 'dashed' ? ' stroke-dasharray=\"6 4\"' : ''\n const markerId = msg.arrowHead === 'filled' ? 'seq-arrow' : 'seq-arrow-open'\n // Bidirectional arrows (`<<->>` / `<<-->>`) reuse the same marker on both\n // ends — the marker defs use orient=\"auto-start-reverse\", which SVG\n // automatically flips 180° for marker-start so it points outward at the\n // line's start instead of reusing the marker-end orientation.\n const markerStart = msg.bidirectional\n ? f` marker-start=\"url(#${markerId})\"`\n : ''\n\n // Semantic wrapper with message metadata\n parts.push(\n f`<g class=\"message\" data-from=\"${escapeAttr(msg.from)}\" data-to=\"${escapeAttr(msg.to)}\" ` +\n f`data-label=\"${escapeAttr(msg.label)}\" data-line-style=\"${msg.lineStyle}\" ` +\n f`data-arrow-head=\"${msg.arrowHead}\" data-self=\"${msg.isSelf}\" data-bidirectional=\"${msg.bidirectional}\">`,\n )\n\n if (msg.isSelf) {\n // Self-message: curved loop going right and back\n // Loop dimensions - loopH is fixed, loopW provides minimum clearance\n const loopW = 30\n const loopH = 20\n const labelPadding = 8 // Space between loop and label\n parts.push(\n f` <polyline points=\"${msg.x1},${msg.y} ${msg.x1 + loopW},${msg.y} ${msg.x1 + loopW},${msg.y + loopH} ${msg.x2},${msg.y + loopH}\" ` +\n f`fill=\"none\" stroke=\"var(--_line)\" stroke-width=\"${STROKE_WIDTHS.connector}\"${dashArray} marker-end=\"url(#${markerId})\"${markerStart} />`,\n )\n // Label to the right of the loop (supports multi-line)\n parts.push(\n ' ' +\n renderMultilineText(\n msg.label,\n msg.x1 + loopW + labelPadding,\n msg.y + loopH / 2,\n fontSizes.edgeLabel,\n f`font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"start\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n } else {\n // Normal message: horizontal arrow\n parts.push(\n f` <line x1=\"${msg.x1}\" y1=\"${msg.y}\" x2=\"${msg.x2}\" y2=\"${msg.y}\" ` +\n f`stroke=\"var(--_line)\" stroke-width=\"${STROKE_WIDTHS.connector}\"${dashArray} marker-end=\"url(#${markerId})\"${markerStart} />`,\n )\n // Label above the arrow, centered (supports multi-line)\n const midX = (msg.x1 + msg.x2) / 2\n parts.push(\n ' ' +\n renderMultilineText(\n msg.label,\n midX,\n msg.y - 10,\n fontSizes.edgeLabel,\n f`font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n }\n\n if (msg.seqNumber !== undefined) {\n parts.push(' ' + renderSeqNumberBadge(msg, fontSizes))\n }\n\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n/**\n * Render the small `autonumber` badge Mermaid draws near the start of a\n * message arrow: a circle with the sequence number centered inside it,\n * layered on top of the arrow rather than folded into the label text.\n */\nfunction renderSeqNumberBadge(\n msg: PositionedMessage,\n fontSizes: FontSizes,\n): string {\n const radius = 8\n const fontSize = Math.max(fontSizes.edgeLabel - 2, 8)\n // Bidirectional messages also draw an arrowhead at x1 (the departure\n // end) — badge-at-x1 would sit on top of it. Shift the badge into the\n // arrow span, clear of the marker, in that case. One-way messages have\n // no marker at x1, so they keep the badge exactly at the departure\n // point as before.\n const cx = msg.bidirectional\n ? msg.x1 + Math.sign(msg.x2 - msg.x1) * (radius + ARROW_HEAD.width)\n : msg.x1\n return (\n `<g class=\"seq-number\">` +\n f`<circle cx=\"${cx}\" cy=\"${msg.y}\" r=\"${radius}\" fill=\"var(--bg)\" stroke=\"var(--_arrow)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />` +\n renderMultilineText(\n String(msg.seqNumber),\n cx,\n msg.y,\n fontSize,\n f`font-size=\"${fontSize}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_arrow)\"`,\n ) +\n `</g>`\n )\n}\n\n/**\n * How much of a `box` colour shows through: the colour is mixed into the\n * theme background at this percentage rather than painted as-is, so\n * `box Purple` reads as a purple tint on a light theme and a deep purple on\n * a dark one instead of the same opaque swatch on both (Mermaid paints the\n * literal colour). The stroke and label use theme variables, so a colourless\n * box still shows its grouping in every theme.\n */\nconst BOX_COLOR_MIX_PERCENT = 35\n\n/**\n * Render a `box … end` participant-group background: a full-height rect\n * behind the grouped lifelines with the label centred in its top band.\n * Wrapped in <g class=\"participant-box\"> with semantic data attributes.\n *\n * `box.color` is emitted verbatim inside `color-mix()` — safe because the\n * parser only records a value that matched its fixed named-colour list or\n * the hex / rgb() / hsl() patterns (see box-color.ts), never arbitrary\n * diagram text.\n */\nfunction renderParticipantBox(\n box: PositionedParticipantBox,\n fontSizes: FontSizes,\n): string {\n const labelAttr = box.label ? f` data-label=\"${escapeAttr(box.label)}\"` : ''\n const colorAttr =\n box.color !== undefined ? f` data-color=\"${escapeAttr(box.color)}\"` : ''\n const fill =\n box.color !== undefined\n ? f`color-mix(in srgb, ${escapeAttr(box.color)} ${BOX_COLOR_MIX_PERCENT}%, var(--bg))`\n : 'none'\n const parts: string[] = [\n f`<g class=\"participant-box\"${labelAttr}${colorAttr}>`,\n f` <rect x=\"${box.x}\" y=\"${box.y}\" width=\"${box.width}\" height=\"${box.height}\" rx=\"4\" ry=\"4\" ` +\n f`fill=\"${fill}\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />`,\n ]\n if (box.label) {\n // Centred in the label band the layout reserved above the actor boxes.\n parts.push(\n ' ' +\n renderMultilineText(\n box.label,\n box.x + box.width / 2,\n box.y + boxLabelHeight(fontSizes.edgeLabel) / 2,\n fontSizes.edgeLabel,\n f`font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.groupHeader}\" fill=\"var(--_text-sec)\"`,\n ),\n )\n }\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n/**\n * Render a block background (loop/alt/opt).\n * Wrapped in <g class=\"block\"> with semantic data attributes.\n */\nfunction renderBlock(block: PositionedBlock, fontSizes: FontSizes): string {\n const parts: string[] = []\n\n // Semantic wrapper with block metadata\n const labelAttr = block.label\n ? f` data-label=\"${escapeAttr(block.label)}\"`\n : ''\n parts.push(\n f`<g class=\"block\" data-type=\"${escapeAttr(block.type)}\"${labelAttr}>`,\n )\n\n // Outer rectangle\n parts.push(\n f` <rect x=\"${block.x}\" y=\"${block.y}\" width=\"${block.width}\" height=\"${block.height}\" ` +\n f`rx=\"0\" ry=\"0\" fill=\"none\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n\n // Type label tab (top-left corner)\n // For multi-line block labels, we use the first line for the tab but show full label\n const labelText = f`${block.type}${block.label ? f` [${block.label}]` : ''}`\n // Audited for issue #100: `String.prototype.split` always returns an\n // array with at least one element (even splitting `''` yields `['']`),\n // so index 0 is guaranteed to exist regardless of whether `labelText`\n // contains a newline. `noUncheckedIndexedAccess` can't see that\n // language-level guarantee; left as-is, no behavior change.\n const firstLine = labelText.split('\\n')[0]!\n const tabWidth =\n estimateTextWidth(\n firstLine,\n fontSizes.edgeLabel,\n FONT_WEIGHTS.groupHeader,\n ) + 16\n const tabHeight = 18\n\n parts.push(\n f` <rect x=\"${block.x}\" y=\"${block.y}\" width=\"${tabWidth}\" height=\"${tabHeight}\" ` +\n f`fill=\"var(--_group-hdr)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.outerBox}\" />`,\n )\n // Block type label (supports multi-line via <br> tags)\n parts.push(\n ' ' +\n renderMultilineText(\n labelText,\n block.x + 6,\n block.y + tabHeight / 2,\n fontSizes.edgeLabel,\n f`font-size=\"${fontSizes.edgeLabel}\" font-weight=\"${FONT_WEIGHTS.groupHeader}\" fill=\"var(--_text-sec)\"`,\n ),\n )\n\n // Divider lines (for alt/else, par/and)\n for (const divider of block.dividers) {\n parts.push(\n f` <line x1=\"${block.x}\" y1=\"${divider.y}\" x2=\"${block.x + block.width}\" y2=\"${divider.y}\" ` +\n `stroke=\"var(--_line)\" stroke-width=\"0.75\" stroke-dasharray=\"6 4\" />`,\n )\n if (divider.label) {\n // Divider label supports multi-line\n parts.push(\n ' ' +\n renderMultilineText(\n f`[${divider.label}]`,\n block.x + 8,\n divider.y + 14,\n fontSizes.edgeLabel,\n f`font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"start\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n ),\n )\n }\n }\n\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n/**\n * Render a note box.\n * Wrapped in <g class=\"note\"> with semantic data attributes.\n */\nfunction renderNote(note: PositionedNote, fontSizes: FontSizes): string {\n // Dog-ear note: polygon with clipped top-right corner + fold triangle\n const foldSize = 6\n const { x, y, width: w, height: h } = note\n\n // Build actor reference attribute if present\n const actorsAttr =\n note.actors && note.actors.length > 0\n ? f` data-actors=\"${note.actors.map(escapeAttr).join(',')}\"`\n : ''\n const positionAttr = note.position\n ? f` data-position=\"${escapeAttr(note.position)}\"`\n : ''\n\n // Note body: polygon with top-right corner cut off\n // (x,y) → (x+w-fold,y) → (x+w,y+fold) → (x+w,y+h) → (x,y+h)\n const bodyPoints = [\n f`${x},${y}`,\n f`${x + w - foldSize},${y}`,\n f`${x + w},${y + foldSize}`,\n f`${x + w},${y + h}`,\n f`${x},${y + h}`,\n ].join(' ')\n\n return (\n f`<g class=\"note\"${positionAttr}${actorsAttr}>` +\n // Note body with bg fill and clipped corner\n f`\\n <polygon points=\"${bodyPoints}\" ` +\n f`fill=\"var(--bg)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />` +\n // Fold triangle (the folded-over corner)\n f`\\n <polygon points=\"${x + w - foldSize},${y} ${x + w},${y + foldSize} ${x + w - foldSize},${y + foldSize}\" ` +\n f`fill=\"var(--_inner-stroke)\" stroke=\"var(--_node-stroke)\" stroke-width=\"${STROKE_WIDTHS.innerBox}\" />` +\n // Note text (supports multi-line)\n f`\\n ${renderMultilineText(\n note.text,\n x + w / 2,\n y + h / 2,\n fontSizes.edgeLabel,\n f`font-size=\"${fontSizes.edgeLabel}\" text-anchor=\"middle\" font-weight=\"${FONT_WEIGHTS.edgeLabel}\" fill=\"var(--_text-muted)\"`,\n )}` +\n `\\n</g>`\n )\n}\n","import type {\n XYChart,\n PositionedXYChart,\n PositionedAxis,\n AxisTick,\n PositionedBar,\n PositionedLine,\n GridLine,\n PlotArea,\n LegendItem,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { XyChartRenderOptions } from '@zombie-mermaid/core'\nimport { estimateTextWidth } from '../styles.ts'\n\n// ============================================================================\n// XY Chart layout engine\n//\n// Computes pixel coordinates for all chart elements. No dagre needed —\n// direct coordinate-space mapping for axes, bars, lines, and grid.\n// ============================================================================\n\n/** Layout constants — aligned with Chart.js default proportions */\nconst XY = {\n plotWidth: 600,\n plotHeight: 340,\n padding: 22,\n titleFontSize: 18,\n titleFontWeight: 600,\n titleHeight: 42,\n axisLabelFontSize: 14,\n axisLabelFontWeight: 400,\n axisTitleFontSize: 15,\n axisTitleFontWeight: 500,\n xLabelHeight: 38,\n yLabelWidth: 58,\n yLabelGap: 18,\n axisTitlePad: 30,\n tickLength: 4,\n barPadRatio: 0.2,\n barGroupGap: 0,\n maxBarWidth: 40,\n legendFontSize: 14,\n legendFontWeight: 400,\n legendHeight: 28,\n legendSwatchW: 14,\n legendSwatchH: 14,\n legendGap: 6,\n legendItemGap: 16,\n} as const\n\n/**\n * Lay out a parsed XY chart by computing pixel coordinates.\n */\nexport function layoutXYChart(\n chart: XYChart,\n _options: XyChartRenderOptions = {},\n): PositionedXYChart {\n if (chart.horizontal) return layoutHorizontal(chart)\n return layoutVertical(chart)\n}\n\n// ============================================================================\n// Vertical layout (default)\n// ============================================================================\n\nfunction layoutVertical(chart: XYChart): PositionedXYChart {\n const hasTitle = !!chart.title\n const hasXTitle = !!chart.xAxis.title\n const hasYTitle = !!chart.yAxis.title\n const hasLegend = chart.series.length > 1\n\n // Compute y-axis label width from tick labels\n //\n // parseXYChart always derives a y-axis range (from data or a 0-100\n // fallback — see xychart/parser.ts) before returning, so this is never\n // actually undefined for parsed input. That guarantee lives in a\n // different module, so narrow it here explicitly instead of trusting it\n // silently across the boundary.\n const yRange = chart.yAxis.range\n if (!yRange) {\n /* v8 ignore next */\n throw new Error('XY chart: y-axis range was not set by the parser')\n }\n const yTicks = niceTickValues(yRange.min, yRange.max)\n const yLabels = formatTickLabels(yTicks)\n const maxYLabelWidth = Math.max(\n ...yLabels.map((label) =>\n estimateTextWidth(label, XY.axisLabelFontSize, XY.axisLabelFontWeight),\n ),\n XY.yLabelWidth,\n )\n\n // Margins\n const top =\n XY.padding +\n (hasTitle ? XY.titleHeight : 0) +\n (hasLegend ? XY.legendHeight : 0)\n const bottom =\n XY.padding + XY.xLabelHeight + (hasXTitle ? XY.axisTitlePad : 0)\n const left =\n XY.padding +\n maxYLabelWidth +\n XY.yLabelGap +\n (hasYTitle ? XY.axisTitlePad : 0)\n const right = XY.padding\n\n const plotW = XY.plotWidth\n const plotH = XY.plotHeight\n const totalW = left + plotW + right\n const totalH = top + plotH + bottom\n\n const plotArea: PlotArea = { x: left, y: top, width: plotW, height: plotH }\n\n // Scales\n const dataCount = getDataCount(chart)\n const xScale = (i: number) => left + (i + 0.5) * (plotW / dataCount)\n const bandWidth = plotW / dataCount\n const yScale = (v: number) => {\n const t = (v - yRange.min) / (yRange.max - yRange.min || 1)\n return top + plotH - t * plotH\n }\n\n // X-axis ticks\n const xTicks = buildXTicks(chart, xScale, top + plotH, bandWidth)\n\n // Y-axis ticks\n const yAxisTicks: AxisTick[] = yTicks.map((v, i) => ({\n label: yLabels[i]!,\n x: left,\n y: yScale(v),\n tx: left - XY.tickLength,\n ty: yScale(v),\n labelX: left - XY.yLabelGap,\n labelY: yScale(v),\n textAnchor: 'end' as const,\n }))\n\n // Grid lines (horizontal at each y tick)\n const gridLines: GridLine[] = yTicks.map((v) => ({\n x1: left,\n y1: yScale(v),\n x2: left + plotW,\n y2: yScale(v),\n }))\n\n // Category labels for data attributes\n const catLabels = getCategoryLabels(chart, dataCount)\n\n // Global color index map: each series gets a unique color index regardless of type\n const colorMap = chart.series.map((_, i) => i)\n\n // Bars\n const bars = layoutBars(\n chart,\n xScale,\n yScale,\n bandWidth,\n yRange.min,\n catLabels,\n colorMap,\n )\n\n // Lines\n const lines = layoutLines(chart, xScale, yScale, catLabels, colorMap)\n\n // Legend\n const legendY =\n XY.padding + (hasTitle ? XY.titleHeight : 0) + XY.legendHeight / 2\n const legend = hasLegend\n ? buildLegendItems(chart, totalW / 2, legendY, colorMap)\n : []\n\n // Axis lines\n const xAxisLine = {\n x1: left,\n y1: top + plotH,\n x2: left + plotW,\n y2: top + plotH,\n }\n const yAxisLine = { x1: left, y1: top, x2: left, y2: top + plotH }\n\n // Axis titles\n const xAxisObj: PositionedAxis = {\n ticks: xTicks,\n line: xAxisLine,\n ...(chart.xAxis.title\n ? {\n title: {\n text: chart.xAxis.title,\n x: left + plotW / 2,\n y: totalH - XY.padding,\n },\n }\n : {}),\n }\n const yAxisObj: PositionedAxis = {\n ticks: yAxisTicks,\n line: yAxisLine,\n ...(chart.yAxis.title\n ? {\n title: {\n text: chart.yAxis.title,\n x: XY.padding + 4,\n y: top + plotH / 2,\n rotate: -90,\n },\n }\n : {}),\n }\n\n // Title\n const titleObj = chart.title\n ? { text: chart.title, x: totalW / 2, y: XY.padding + XY.titleFontSize }\n : undefined\n\n return {\n width: totalW,\n height: totalH,\n title: titleObj,\n xAxis: xAxisObj,\n yAxis: yAxisObj,\n plotArea,\n bars,\n lines,\n gridLines,\n legend,\n }\n}\n\n// ============================================================================\n// Horizontal layout\n// ============================================================================\n\nfunction layoutHorizontal(chart: XYChart): PositionedXYChart {\n const hasTitle = !!chart.title\n const hasXTitle = !!chart.xAxis.title\n const hasYTitle = !!chart.yAxis.title\n const hasLegend = chart.series.length > 1\n\n // In horizontal mode: categories go on y-axis (left side), values go on x-axis (bottom)\n //\n // See the matching comment in layoutVertical: the parser always derives a\n // y-axis range before returning, but that guarantee lives in a different\n // module, so it's narrowed here explicitly rather than trusted silently.\n const yRange = chart.yAxis.range\n if (!yRange) {\n /* v8 ignore next */\n throw new Error('XY chart: y-axis range was not set by the parser')\n }\n const valueTicks = niceTickValues(yRange.min, yRange.max)\n const valueLabels = formatTickLabels(valueTicks)\n\n // Compute category label widths for left margin\n const dataCount = getDataCount(chart)\n const catLabels = getCategoryLabels(chart, dataCount)\n const maxCatLabelWidth = Math.max(\n ...catLabels.map((l) =>\n estimateTextWidth(l, XY.axisLabelFontSize, XY.axisLabelFontWeight),\n ),\n 40,\n )\n\n const top =\n XY.padding +\n (hasTitle ? XY.titleHeight : 0) +\n (hasLegend ? XY.legendHeight : 0)\n const bottom =\n XY.padding + XY.xLabelHeight + (hasYTitle ? XY.axisTitlePad : 0)\n const left =\n XY.padding +\n maxCatLabelWidth +\n XY.yLabelGap +\n (hasXTitle ? XY.axisTitlePad : 0)\n const right = XY.padding\n\n const plotW = XY.plotWidth\n const plotH = XY.plotHeight\n const totalW = left + plotW + right\n const totalH = top + plotH + bottom\n\n const plotArea: PlotArea = { x: left, y: top, width: plotW, height: plotH }\n\n // Value scale (horizontal: left to right)\n const valueScale = (v: number) => {\n const t = (v - yRange.min) / (yRange.max - yRange.min || 1)\n return left + t * plotW\n }\n\n // Category scale (vertical: top to bottom)\n const bandHeight = plotH / dataCount\n const catScale = (i: number) => top + (i + 0.5) * bandHeight\n\n // X-axis (bottom): value ticks\n const xTicks: AxisTick[] = valueTicks.map((v, i) => ({\n label: valueLabels[i]!,\n x: valueScale(v),\n y: top + plotH,\n tx: valueScale(v),\n ty: top + plotH + XY.tickLength,\n labelX: valueScale(v),\n labelY: top + plotH + 18,\n textAnchor: 'middle' as const,\n }))\n\n // Y-axis (left): category ticks\n const yTicks: AxisTick[] = catLabels.map((label, i) => ({\n label,\n x: left,\n y: catScale(i),\n tx: left - XY.tickLength,\n ty: catScale(i),\n labelX: left - XY.yLabelGap,\n labelY: catScale(i),\n textAnchor: 'end' as const,\n }))\n\n // Grid lines (vertical at each value tick)\n const gridLines: GridLine[] = valueTicks.map((v) => ({\n x1: valueScale(v),\n y1: top,\n x2: valueScale(v),\n y2: top + plotH,\n }))\n\n // Global color index map\n const colorMap = chart.series.map((_, i) => i)\n\n // Bars (horizontal)\n const barSeries = chart.series.filter((s) => s.type === 'bar')\n const barCount = barSeries.length\n const bars: PositionedBar[] = []\n if (barCount > 0) {\n const usable = bandHeight * (1 - XY.barPadRatio)\n const rawBarH =\n barCount > 1\n ? (usable - (barCount - 1) * XY.barGroupGap) / barCount\n : usable\n const singleBarH = Math.min(rawBarH, XY.maxBarWidth)\n const groupH =\n barCount > 1\n ? singleBarH * barCount + XY.barGroupGap * (barCount - 1)\n : singleBarH\n let bIdx = 0\n let seriesArrayIdx = 0\n for (const s of chart.series) {\n if (s.type !== 'bar') {\n seriesArrayIdx++\n continue\n }\n for (let i = 0; i < s.data.length; i++) {\n const cy = catScale(i)\n const groupTop = cy - groupH / 2\n const by = groupTop + bIdx * (singleBarH + XY.barGroupGap)\n const valX = valueScale(Math.max(s.data[i]!, yRange.min))\n const baseX = valueScale(Math.max(0, yRange.min))\n bars.push({\n x: Math.min(baseX, valX),\n y: by,\n width: Math.abs(valX - baseX),\n height: singleBarH,\n value: s.data[i]!,\n // A series can carry more data points than the chart has category\n // labels for (e.g. a bar/line series longer than x-axis.categories\n // or the series that determined dataCount), so catLabels[i] is not\n // guaranteed to exist just because s.data[i] does. `label` is\n // already optional on PositionedBar, so let a missing entry flow\n // through as undefined instead of asserting past a real gap.\n label: catLabels[i],\n seriesIndex: bIdx,\n colorIndex: colorMap[seriesArrayIdx]!,\n })\n }\n bIdx++\n seriesArrayIdx++\n }\n }\n\n // Lines (horizontal: value on x, category index on y)\n const lines: PositionedLine[] = []\n let lineIdx = 0\n let lineSeriesIdx = 0\n for (const s of chart.series) {\n if (s.type !== 'line') {\n lineSeriesIdx++\n continue\n }\n const points = s.data.map((v, i) => ({\n x: valueScale(v),\n y: catScale(i),\n value: v,\n // See the matching comment in the bars loop above: a line series can\n // outrun catLabels, and the point's `label` is already optional.\n label: catLabels[i],\n }))\n lines.push({\n points,\n seriesIndex: lineIdx,\n colorIndex: colorMap[lineSeriesIdx]!,\n })\n lineIdx++\n lineSeriesIdx++\n }\n\n const xAxisLine = {\n x1: left,\n y1: top + plotH,\n x2: left + plotW,\n y2: top + plotH,\n }\n const yAxisLine = { x1: left, y1: top, x2: left, y2: top + plotH }\n\n // In horizontal mode, the \"y-axis\" title describes values (bottom) and \"x-axis\" title describes categories (left)\n const xAxisObj: PositionedAxis = {\n ticks: xTicks,\n line: xAxisLine,\n ...(chart.yAxis.title\n ? {\n title: {\n text: chart.yAxis.title,\n x: left + plotW / 2,\n y: totalH - XY.padding,\n },\n }\n : {}),\n }\n const yAxisObj: PositionedAxis = {\n ticks: yTicks,\n line: yAxisLine,\n ...(chart.xAxis.title\n ? {\n title: {\n text: chart.xAxis.title,\n x: XY.padding + 4,\n y: top + plotH / 2,\n rotate: -90,\n },\n }\n : {}),\n }\n\n const titleObj = chart.title\n ? { text: chart.title, x: totalW / 2, y: XY.padding + XY.titleFontSize }\n : undefined\n\n // Legend\n const legendY =\n XY.padding + (hasTitle ? XY.titleHeight : 0) + XY.legendHeight / 2\n const legend = hasLegend\n ? buildLegendItems(chart, totalW / 2, legendY, colorMap)\n : []\n\n return {\n width: totalW,\n height: totalH,\n horizontal: true,\n title: titleObj,\n xAxis: xAxisObj,\n yAxis: yAxisObj,\n plotArea,\n bars,\n lines,\n gridLines,\n legend,\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction getDataCount(chart: XYChart): number {\n if (chart.xAxis.categories) return chart.xAxis.categories.length\n // For numeric range, use the length of the first series\n for (const s of chart.series) {\n if (s.data.length > 0) return s.data.length\n }\n return 1\n}\n\nfunction getCategoryLabels(chart: XYChart, count: number): string[] {\n if (chart.xAxis.categories) return chart.xAxis.categories\n if (chart.xAxis.range) {\n const { min, max } = chart.xAxis.range\n const step = count > 1 ? (max - min) / (count - 1) : 0\n return Array.from({ length: count }, (_, i) =>\n formatTickValue(min + step * i),\n )\n }\n return Array.from({ length: count }, (_, i) => String(i + 1))\n}\n\nfunction buildXTicks(\n chart: XYChart,\n xScale: (i: number) => number,\n axisY: number,\n _bandWidth: number,\n): AxisTick[] {\n const count = getDataCount(chart)\n const labels = getCategoryLabels(chart, count)\n return labels.map((label, i) => ({\n label,\n x: xScale(i),\n y: axisY,\n tx: xScale(i),\n ty: axisY + XY.tickLength,\n labelX: xScale(i),\n labelY: axisY + 18,\n textAnchor: 'middle' as const,\n }))\n}\n\nfunction layoutBars(\n chart: XYChart,\n xScale: (i: number) => number,\n yScale: (v: number) => number,\n bandWidth: number,\n yMin: number,\n catLabels: string[],\n colorMap: number[],\n): PositionedBar[] {\n const barSeries = chart.series.filter((s) => s.type === 'bar')\n const barCount = barSeries.length\n if (barCount === 0) return []\n\n const usable = bandWidth * (1 - XY.barPadRatio)\n const rawBarW =\n barCount > 1\n ? (usable - (barCount - 1) * XY.barGroupGap) / barCount\n : usable\n const singleBarW = Math.min(rawBarW, XY.maxBarWidth)\n const groupW =\n barCount > 1\n ? singleBarW * barCount + XY.barGroupGap * (barCount - 1)\n : singleBarW\n const bars: PositionedBar[] = []\n\n let bIdx = 0\n let seriesArrayIdx = 0\n for (const s of chart.series) {\n if (s.type !== 'bar') {\n seriesArrayIdx++\n continue\n }\n for (let i = 0; i < s.data.length; i++) {\n const cx = xScale(i)\n const groupLeft = cx - groupW / 2\n const bx = groupLeft + bIdx * (singleBarW + XY.barGroupGap)\n const valY = yScale(s.data[i]!)\n const baseY = yScale(Math.max(0, yMin))\n bars.push({\n x: bx,\n y: Math.min(valY, baseY),\n width: singleBarW,\n height: Math.abs(baseY - valY),\n value: s.data[i]!,\n // catLabels has one entry per dataCount, which is not necessarily\n // s.data.length for every series. `label` is already optional on\n // PositionedBar, so let a missing entry flow through as undefined\n // instead of asserting past a real (if unusual) length mismatch.\n // See layoutHorizontal's matching comment.\n label: catLabels[i],\n seriesIndex: bIdx,\n colorIndex: colorMap[seriesArrayIdx]!,\n })\n }\n bIdx++\n seriesArrayIdx++\n }\n return bars\n}\n\nfunction layoutLines(\n chart: XYChart,\n xScale: (i: number) => number,\n yScale: (v: number) => number,\n catLabels: string[],\n colorMap: number[],\n): PositionedLine[] {\n const lines: PositionedLine[] = []\n let lineIdx = 0\n let seriesArrayIdx = 0\n for (const s of chart.series) {\n if (s.type !== 'line') {\n seriesArrayIdx++\n continue\n }\n const points = s.data.map((v, i) => ({\n x: xScale(i),\n y: yScale(v),\n value: v,\n // See layoutBars' matching comment: a line series can outrun\n // catLabels, and the point's `label` is already optional.\n label: catLabels[i],\n }))\n lines.push({\n points,\n seriesIndex: lineIdx,\n colorIndex: colorMap[seriesArrayIdx]!,\n })\n lineIdx++\n seriesArrayIdx++\n }\n return lines\n}\n\n/** Generate \"nice\" tick values for a numeric range */\nexport function niceTickValues(min: number, max: number): number[] {\n const range = max - min\n if (range <= 0) return [min]\n\n // Find nice interval\n const rawInterval = range / 6\n const magnitude = Math.pow(10, Math.floor(Math.log10(rawInterval)))\n const residual = rawInterval / magnitude\n let niceInterval: number\n if (residual <= 1.5) niceInterval = magnitude\n else if (residual <= 3) niceInterval = 2 * magnitude\n else if (residual <= 7) niceInterval = 5 * magnitude\n else niceInterval = 10 * magnitude\n\n const start = Math.ceil(min / niceInterval) * niceInterval\n const ticks: number[] = []\n for (let v = start; v <= max + niceInterval * 0.001; v += niceInterval) {\n ticks.push(Math.round(v * 1e10) / 1e10) // avoid floating-point noise\n }\n return ticks\n}\n\n/**\n * Format a set of evenly-spaced tick values with enough decimal places to\n * keep every label distinct. Precision comes from the tick step, not the\n * magnitude of the value: a 0.5 step around 100 needs one decimal, or\n * 99.5 and 100.5 both collapse into neighbouring whole numbers.\n */\nexport function formatTickLabels(ticks: number[]): string[] {\n const step = ticks.length > 1 ? Math.abs(ticks[1]! - ticks[0]!) : 0\n // The -1e-9 keeps an exact power-of-ten step (0.1) from rounding up\n const decimals =\n step > 0 && step < 1 ? Math.ceil(-Math.log10(step) - 1e-9) : 0\n return ticks.map((v) =>\n Number.isInteger(v) && decimals === 0\n ? String(v)\n : String(Number(v.toFixed(decimals))),\n )\n}\n\nfunction formatTickValue(v: number): string {\n if (Number.isInteger(v)) return String(v)\n // Limit decimal places\n return v.toFixed(Math.abs(v) < 10 ? 1 : 0)\n}\n\n/** Build centered legend items for multi-series charts */\nfunction buildLegendItems(\n chart: XYChart,\n centerX: number,\n y: number,\n colorMap: number[],\n): LegendItem[] {\n const items: LegendItem[] = []\n let barIdx = 0,\n lineIdx = 0\n for (let si = 0; si < chart.series.length; si++) {\n const s = chart.series[si]!\n const label = s.type === 'bar' ? `Bar ${barIdx + 1}` : `Line ${lineIdx + 1}`\n items.push({\n label,\n x: 0,\n y,\n type: s.type,\n seriesIndex: s.type === 'bar' ? barIdx : lineIdx,\n colorIndex: colorMap[si]!,\n })\n if (s.type === 'bar') barIdx++\n else lineIdx++\n }\n\n // Measure total width, then center\n const itemWidths = items.map((item) => {\n const textW = estimateTextWidth(\n item.label,\n XY.legendFontSize,\n XY.legendFontWeight,\n )\n return XY.legendSwatchW + XY.legendGap + textW\n })\n const totalWidth =\n itemWidths.reduce((a, b) => a + b, 0) +\n (items.length - 1) * XY.legendItemGap\n let x = centerX - totalWidth / 2\n\n for (let i = 0; i < items.length; i++) {\n items[i]!.x = x\n x += itemWidths[i]! + XY.legendItemGap\n }\n\n return items\n}\n","import type { PositionedXYChart } from '@zombie-mermaid/mermaid-parser'\nimport type { DiagramColors, SvgEmitOptions } from '@zombie-mermaid/core'\nimport { svgOpenTag, buildStyleBlock, styleOpenTag } from '@zombie-mermaid/core'\nimport { withDataSrc } from '../renderer.ts'\nimport { TEXT_BASELINE_SHIFT, estimateTextWidth } from '../styles.ts'\nimport {\n getSeriesColor,\n CHART_ACCENT_FALLBACK,\n} from '@zombie-mermaid/mermaid-parser'\n\n// ============================================================================\n// XY Chart SVG renderer\n//\n// Renders positioned XY charts to SVG strings.\n// All colors use CSS custom properties (var(--_xxx)) from the theme system.\n//\n// Visual style: clean, minimal, modern. Inspired by Apple/Craft chart design.\n// - No axis lines or tick marks — labels float freely\n// - Ultra-subtle solid grid lines\n// - Bars with rounded tops, flat at baseline\n// - Smooth curved lines, no visible dots (dots appear on hover)\n//\n// Render order (back to front):\n// 1. Grid lines\n// 2. Bars (as paths with rounded tops)\n// 3. Lines (smooth curves)\n// 4. Dots (hidden by default, visible on hover when interactive)\n// 5. Axis labels\n// 6. Axis titles\n// 7. Chart title\n// 8. Legend\n// ============================================================================\n\nconst CHART_FONT = {\n titleSize: 18,\n titleWeight: 600,\n axisTitleSize: 15,\n axisTitleWeight: 500,\n labelSize: 14,\n labelWeight: 400,\n legendSize: 14,\n legendWeight: 400,\n dotRadius: 5,\n lineWidth: 2.5,\n barRadius: 8,\n} as const\n\nconst TIP = {\n fontSize: 15,\n fontWeight: 500,\n height: 32,\n padX: 14,\n offsetY: 12,\n rx: 8,\n minY: 4,\n pointerSize: 6,\n} as const\n\n/**\n * Render a positioned XY chart as an SVG string.\n *\n * @param embedSource - Original diagram source to stamp onto the root\n * `<svg>` as `data-src` (from `options.embedSource`).\n * Omitted when the option is off.\n * @param title - Accessible name (from `options.title`). See svgOpenTag() in\n * packages/core/src/theme.ts.\n * @param decorative - Marks the SVG decorative (from `options.decorative`).\n * @param emit - Strict-CSP controls (from `options.nonce` /\n * `options.styleAttribute`, see #216). Default: no nonce,\n * root `style` attribute on.\n */\nexport function renderXYChartSvg(\n chart: PositionedXYChart,\n colors: DiagramColors,\n font: string = 'Inter',\n transparent: boolean = false,\n interactive: boolean = false,\n embedSource?: string,\n title?: string,\n decorative?: boolean,\n emit: SvgEmitOptions = {},\n): string {\n const parts: string[] = []\n\n // SVG root + base styles\n // Stamp data-xychart-colors so theme-switching JS knows how many series color vars to update\n const maxColorIdx = Math.max(\n 0,\n ...chart.bars.map((b) => b.colorIndex),\n ...chart.lines.map((l) => l.colorIndex),\n )\n const svgTag = withDataSrc(\n svgOpenTag(\n chart.width,\n chart.height,\n colors,\n transparent,\n title,\n decorative,\n undefined,\n emit.styleAttribute,\n ).replace('<svg ', `<svg data-xychart-colors=\"${maxColorIdx}\" `),\n embedSource,\n )\n parts.push(svgTag)\n parts.push(buildStyleBlock(font, false, emit.nonce))\n\n // Sparse lines (≤12 points) show dots by default\n const maxLinePoints = Math.max(...chart.lines.map((l) => l.points.length), 0)\n const sparse = maxLinePoints > 0 && maxLinePoints <= 12\n\n // Chart-specific styles + gradient defs\n const { style: chartCss, defs: chartDefs } = chartStyles(\n chart,\n interactive,\n sparse,\n colors.accent,\n colors.bg,\n emit.nonce,\n )\n parts.push(chartCss)\n if (chartDefs) parts.push(chartDefs)\n\n // 1. Dot grid (dense dots across plot area, aligned to tick spacing)\n const { plotArea } = chart\n const xTicks = chart.xAxis.ticks.map((t) => t.x)\n const yVals = chart.horizontal\n ? chart.yAxis.ticks.map((t) => t.y)\n : chart.gridLines.map((g) => g.y1)\n const xBase =\n xTicks.length > 1 ? Math.abs(xTicks[1]! - xTicks[0]!) : plotArea.width / 6\n const yBase =\n yVals.length > 1 ? Math.abs(yVals[1]! - yVals[0]!) : plotArea.height / 6\n const xGap = xBase / Math.max(1, Math.round(xBase / 20))\n const yGap = yBase / Math.max(1, Math.round(yBase / 20))\n const xAnchor = xTicks[0] ?? plotArea.x\n const yAnchor = yVals[0] ?? plotArea.y\n const xStart = xAnchor - Math.ceil((xAnchor - plotArea.x) / xGap) * xGap\n const yStart = yAnchor - Math.ceil((yAnchor - plotArea.y) / yGap) * yGap\n for (let y = yStart; y <= plotArea.y + plotArea.height + 0.5; y += yGap) {\n for (let x = xStart; x <= plotArea.x + plotArea.width + 0.5; x += xGap) {\n parts.push(\n `<circle cx=\"${r(x)}\" cy=\"${r(y)}\" r=\"1.5\" class=\"xychart-grid\"/>`,\n )\n }\n }\n\n // 2. Bars — always render bar paths inline (before lines for correct z-order)\n // Interactive: also build overlay groups with transparent hit-areas + tooltips (deferred to step 9)\n const barOverlay: string[] = []\n for (const bar of chart.bars) {\n const dataAttrs = ` data-value=\"${bar.value}\"${bar.label ? ` data-label=\"${escapeXml(bar.label)}\"` : ''}`\n const barPath = chart.horizontal\n ? roundedRightBarPath(\n bar.x,\n bar.y,\n bar.width,\n bar.height,\n CHART_FONT.barRadius,\n )\n : roundedTopBarPath(\n bar.x,\n bar.y,\n bar.width,\n bar.height,\n CHART_FONT.barRadius,\n )\n parts.push(\n `<path d=\"${barPath}\" class=\"xychart-bar xychart-color-${bar.colorIndex}\"${dataAttrs}/>`,\n )\n if (interactive) {\n const tipText = formatTipValue(bar.value)\n const tipTitle = bar.label ? `${bar.label}: ${tipText}` : tipText\n const tip = tooltipAbove(bar.x + bar.width / 2, bar.y, tipText)\n barOverlay.push(\n `<g class=\"xychart-bar-group\">` +\n `<rect x=\"${r(bar.x)}\" y=\"${r(bar.y)}\" width=\"${r(bar.width)}\" height=\"${r(bar.height)}\" fill=\"transparent\"/>` +\n `<title>${escapeXml(tipTitle)}</title>` +\n tip +\n `</g>`,\n )\n }\n }\n\n // 3. Lines — shadow first (wider, low opacity), then crisp line on top\n for (const line of chart.lines) {\n if (line.points.length === 0) continue\n const d = smoothCurvePath(line.points)\n parts.push(\n `<path d=\"${d}\" class=\"xychart-line-shadow xychart-color-${line.colorIndex}\" transform=\"translate(0,2)\"/>`,\n )\n parts.push(\n `<path d=\"${d}\" class=\"xychart-line xychart-color-${line.colorIndex}\"/>`,\n )\n }\n\n // 4. Dots — grouped by x-position; interactive groups deferred to overlay\n const dotOverlay: string[] = []\n if (interactive || sparse) {\n // Build legend label lookup: line seriesIndex → \"Line 1\", \"Line 2\", etc.\n const lineLegendLabels = new Map<number, string>()\n for (const item of chart.legend) {\n if (item.type === 'line')\n lineLegendLabels.set(item.seriesIndex, item.label)\n }\n\n type DotEntry = {\n x: number\n y: number\n value: number\n label?: string\n seriesIndex: number\n colorIndex: number\n }\n const columns = new Map<string, DotEntry[]>()\n\n for (const line of chart.lines) {\n for (const p of line.points) {\n const key = r(p.x)\n const column = columns.get(key) ?? []\n columns.set(key, column)\n column.push({\n x: p.x,\n y: p.y,\n value: p.value,\n label: p.label,\n seriesIndex: line.seriesIndex,\n colorIndex: line.colorIndex,\n })\n }\n }\n\n // Every array stored in `columns` was created and immediately pushed to\n // above, so `entries` is never empty here — safe to index entries[0]\n // below without an extra length check.\n for (const entries of columns.values()) {\n const cx = entries[0]!.x\n const label = entries[0]!.label || ''\n\n if (interactive && entries.length > 1) {\n const topY = Math.min(...entries.map((e) => e.y))\n const botY = Math.max(...entries.map((e) => e.y))\n const hitPad = CHART_FONT.dotRadius * 3\n const hitArea = `<rect x=\"${r(cx - hitPad)}\" y=\"${r(topY - hitPad)}\" width=\"${r(hitPad * 2)}\" height=\"${r(botY - topY + hitPad * 2)}\" fill=\"transparent\" class=\"xychart-hit\"/>`\n const tipEntries = entries.map((e) => ({\n text: formatTipValue(e.value),\n legendLabel:\n lineLegendLabels.get(e.seriesIndex) || `Line ${e.seriesIndex + 1}`,\n }))\n const tip = multiTooltipAbove(\n cx,\n topY - CHART_FONT.dotRadius,\n label,\n tipEntries,\n )\n const valStrs = tipEntries.map((e) => e.text)\n const titleText = label\n ? `${label}: ${valStrs.join(' · ')}`\n : valStrs.join(' · ')\n\n let group = `<g class=\"xychart-dot-group\">${hitArea}`\n for (const e of entries) {\n const dataAttrs = ` data-value=\"${e.value}\"${e.label ? ` data-label=\"${escapeXml(e.label)}\"` : ''}`\n group += `<circle cx=\"${r(e.x)}\" cy=\"${r(e.y)}\" r=\"${CHART_FONT.dotRadius}\" class=\"xychart-dot xychart-color-${e.colorIndex}\"${dataAttrs}/>`\n }\n group += `<title>${escapeXml(titleText)}</title>${tip}</g>`\n dotOverlay.push(group)\n } else if (interactive) {\n const e = entries[0]!\n const dataAttrs = ` data-value=\"${e.value}\"${e.label ? ` data-label=\"${escapeXml(e.label)}\"` : ''}`\n const tipText = formatTipValue(e.value)\n const tipTitle = e.label ? `${e.label}: ${tipText}` : tipText\n const tip = tooltipAbove(cx, e.y - CHART_FONT.dotRadius, tipText)\n const hitArea = sparse\n ? `<circle cx=\"${r(cx)}\" cy=\"${r(e.y)}\" r=\"${CHART_FONT.dotRadius * 3}\" fill=\"transparent\" class=\"xychart-hit\"/>`\n : ''\n dotOverlay.push(\n `<g class=\"xychart-dot-group\">${hitArea}` +\n `<circle cx=\"${r(e.x)}\" cy=\"${r(e.y)}\" r=\"${CHART_FONT.dotRadius}\" class=\"xychart-dot xychart-color-${e.colorIndex}\"${dataAttrs}/>` +\n `<title>${escapeXml(tipTitle)}</title>${tip}</g>`,\n )\n } else {\n // Sparse, not interactive: static dots render inline\n for (const e of entries) {\n const dataAttrs = ` data-value=\"${e.value}\"${e.label ? ` data-label=\"${escapeXml(e.label)}\"` : ''}`\n parts.push(\n `<circle cx=\"${r(e.x)}\" cy=\"${r(e.y)}\" r=\"${CHART_FONT.dotRadius}\" class=\"xychart-dot xychart-color-${e.colorIndex}\"${dataAttrs}/>`,\n )\n }\n }\n }\n }\n\n // 5. Axis labels (no axis lines, no tick marks — just floating labels)\n for (const tick of chart.xAxis.ticks) {\n parts.push(\n `<text x=\"${tick.labelX}\" y=\"${tick.labelY}\" text-anchor=\"${tick.textAnchor}\" ` +\n `font-size=\"${CHART_FONT.labelSize}\" font-weight=\"${CHART_FONT.labelWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-label\">${escapeXml(tick.label)}</text>`,\n )\n }\n for (const tick of chart.yAxis.ticks) {\n parts.push(\n `<text x=\"${tick.labelX}\" y=\"${tick.labelY}\" text-anchor=\"${tick.textAnchor}\" ` +\n `font-size=\"${CHART_FONT.labelSize}\" font-weight=\"${CHART_FONT.labelWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-label\">${escapeXml(tick.label)}</text>`,\n )\n }\n\n // 6. Axis titles\n if (chart.xAxis.title) {\n const t = chart.xAxis.title\n const transform = t.rotate\n ? ` transform=\"rotate(${t.rotate},${t.x},${t.y})\"`\n : ''\n parts.push(\n `<text x=\"${t.x}\" y=\"${t.y}\" text-anchor=\"middle\"${transform} ` +\n `font-size=\"${CHART_FONT.axisTitleSize}\" font-weight=\"${CHART_FONT.axisTitleWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-axis-title\">${escapeXml(t.text)}</text>`,\n )\n }\n if (chart.yAxis.title) {\n const t = chart.yAxis.title\n const transform = t.rotate\n ? ` transform=\"rotate(${t.rotate},${t.x},${t.y})\"`\n : ''\n parts.push(\n `<text x=\"${t.x}\" y=\"${t.y}\" text-anchor=\"middle\"${transform} ` +\n `font-size=\"${CHART_FONT.axisTitleSize}\" font-weight=\"${CHART_FONT.axisTitleWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-axis-title\">${escapeXml(t.text)}</text>`,\n )\n }\n\n // 7. Chart title\n if (chart.title) {\n parts.push(\n `<text x=\"${chart.title.x}\" y=\"${chart.title.y}\" text-anchor=\"middle\" ` +\n `font-size=\"${CHART_FONT.titleSize}\" font-weight=\"${CHART_FONT.titleWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-title\">${escapeXml(chart.title.text)}</text>`,\n )\n }\n\n // 8. Legend\n for (const item of chart.legend) {\n const swatchW = 14,\n swatchH = 14\n const gap = 6\n if (item.type === 'bar') {\n parts.push(\n `<rect x=\"${item.x}\" y=\"${item.y - swatchH / 2}\" width=\"${swatchW}\" height=\"${swatchH}\" rx=\"3\" ` +\n `class=\"xychart-bar xychart-color-${item.colorIndex}\"/>`,\n )\n } else {\n const ly = item.y\n parts.push(\n `<line x1=\"${item.x}\" y1=\"${ly}\" x2=\"${item.x + swatchW}\" y2=\"${ly}\" ` +\n `stroke-width=\"${CHART_FONT.lineWidth}\" stroke-linecap=\"round\" class=\"xychart-legend-line xychart-color-${item.colorIndex}\"/>`,\n )\n }\n parts.push(\n `<text x=\"${item.x + swatchW + gap}\" y=\"${item.y}\" text-anchor=\"start\" ` +\n `font-size=\"${CHART_FONT.legendSize}\" font-weight=\"${CHART_FONT.legendWeight}\" ` +\n `dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-label\">${escapeXml(item.label)}</text>`,\n )\n }\n\n // 9. Interactive overlay — rendered last so tooltips are always on top\n for (const g of barOverlay) parts.push(g)\n for (const g of dotOverlay) parts.push(g)\n\n parts.push('</svg>')\n return parts.join('\\n')\n}\n\n// ============================================================================\n// Chart-specific CSS styles\n// ============================================================================\n\nfunction chartStyles(\n chart: PositionedXYChart,\n interactive: boolean,\n sparse: boolean,\n themeAccent?: string,\n bgColor?: string,\n nonce?: string,\n): { style: string; defs: string } {\n const accentHex = themeAccent ?? CHART_ACCENT_FALLBACK\n\n // Collect all unique global color indices from bars + lines\n const colorIndices = new Set<number>()\n for (const b of chart.bars) colorIndices.add(b.colorIndex)\n for (const l of chart.lines) colorIndices.add(l.colorIndex)\n\n // Define --xychart-color-N CSS custom properties (updatable by theme-switching JS)\n // Also define --xychart-bar-fill-N via color-mix() so it stays dynamic on theme change\n const colorVarDefs: string[] = []\n for (const idx of [...colorIndices].sort((a, b) => a - b)) {\n const value =\n idx === 0\n ? `var(--accent, ${CHART_ACCENT_FALLBACK})`\n : getSeriesColor(idx, accentHex, bgColor)\n colorVarDefs.push(` --xychart-color-${idx}: ${value};`)\n colorVarDefs.push(\n ` --xychart-bar-fill-${idx}: color-mix(in srgb, var(--bg) 75%, var(--xychart-color-${idx}) 25%);`,\n )\n }\n\n // Generate unified color rules — one per global index, referencing the CSS vars\n const seriesRules: string[] = []\n for (const idx of [...colorIndices].sort((a, b) => a - b)) {\n const color = `var(--xychart-color-${idx})`\n // Bar-specific: stroke + solid blended fill (no opacity)\n seriesRules.push(\n ` .xychart-bar.xychart-color-${idx} { stroke: ${color}; fill: var(--xychart-bar-fill-${idx}); }`,\n )\n // Line/dot-specific: stroke for paths, fill for circles\n seriesRules.push(\n ` path.xychart-color-${idx}, line.xychart-color-${idx} { stroke: ${color}; }`,\n )\n seriesRules.push(` circle.xychart-color-${idx} { fill: ${color}; }`)\n }\n\n const tipRules = interactive\n ? `\n .xychart-tip { opacity: 0; pointer-events: none; }\n .xychart-tip-bg { fill: var(--_text); filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--fg) 20%, transparent)); }\n .xychart-tip-text { fill: var(--bg); font-size: ${TIP.fontSize}px; font-weight: ${TIP.fontWeight}; }\n .xychart-tip-ptr { fill: var(--_text); }\n .xychart-bar-group:hover .xychart-tip,\n .xychart-dot-group:hover .xychart-tip { opacity: 1; }`\n : ''\n\n const colorVarsBlock =\n colorVarDefs.length > 0 ? `\\n svg {\\n${colorVarDefs.join('\\n')}\\n }` : ''\n\n const style = `${styleOpenTag(nonce)}\n .xychart-grid { fill: var(--_inner-stroke); stroke: none; opacity: 0.65; }\n .xychart-bar { stroke-width: 1.5; }\n .xychart-line { fill: none; stroke-width: ${CHART_FONT.lineWidth}; stroke-linecap: round; stroke-linejoin: round; }\n .xychart-line-shadow { fill: none; stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; opacity: 0.12; }\n .xychart-dot { stroke: var(--bg); stroke-width: 2; }\n .xychart-label { fill: var(--_text-muted); }\n .xychart-axis-title { fill: var(--_text-sec); }\n .xychart-title { fill: var(--_text); }${colorVarsBlock}\n${seriesRules.join('\\n')}${tipRules}\n</style>`\n\n return { style, defs: '' }\n}\n\n// ============================================================================\n// Bar path with all corners rounded\n// ============================================================================\n\nfunction roundedTopBarPath(\n x: number,\n y: number,\n w: number,\n h: number,\n radius: number,\n): string {\n const rr = Math.min(radius, w / 2, h / 2)\n if (rr <= 0) {\n return `M${r(x)},${r(y)} h${r(w)} v${r(h)} h${r(-w)} Z`\n }\n return [\n `M${r(x)},${r(y + rr)}`, // start below top-left\n `Q${r(x)},${r(y)} ${r(x + rr)},${r(y)}`, // top-left\n `L${r(x + w - rr)},${r(y)}`, // top edge\n `Q${r(x + w)},${r(y)} ${r(x + w)},${r(y + rr)}`, // top-right\n `L${r(x + w)},${r(y + h - rr)}`, // right edge\n `Q${r(x + w)},${r(y + h)} ${r(x + w - rr)},${r(y + h)}`, // bottom-right\n `L${r(x + rr)},${r(y + h)}`, // bottom edge\n `Q${r(x)},${r(y + h)} ${r(x)},${r(y + h - rr)}`, // bottom-left\n 'Z',\n ].join(' ')\n}\n\n// ============================================================================\n// Bar path with all corners rounded (for horizontal charts)\n// ============================================================================\n\nfunction roundedRightBarPath(\n x: number,\n y: number,\n w: number,\n h: number,\n radius: number,\n): string {\n const rr = Math.min(radius, w / 2, h / 2)\n if (rr <= 0) {\n return `M${r(x)},${r(y)} h${r(w)} v${r(h)} h${r(-w)} Z`\n }\n return [\n `M${r(x + rr)},${r(y)}`, // start after top-left\n `L${r(x + w - rr)},${r(y)}`, // top edge\n `Q${r(x + w)},${r(y)} ${r(x + w)},${r(y + rr)}`, // top-right\n `L${r(x + w)},${r(y + h - rr)}`, // right edge\n `Q${r(x + w)},${r(y + h)} ${r(x + w - rr)},${r(y + h)}`, // bottom-right\n `L${r(x + rr)},${r(y + h)}`, // bottom edge\n `Q${r(x)},${r(y + h)} ${r(x)},${r(y + h - rr)}`, // bottom-left\n `L${r(x)},${r(y + rr)}`, // left edge\n `Q${r(x)},${r(y)} ${r(x + rr)},${r(y)}`, // top-left\n 'Z',\n ].join(' ')\n}\n\n// ============================================================================\n// Smooth line interpolation — monotone cubic (Fritsch-Carlson / PCHIP)\n//\n// A natural cubic spline is smooth but not shape-preserving: near a sharp\n// spike it overshoots the data, drawing a peak above the maximum or a dip\n// below the minimum (even below a zero baseline). Monotone cubic\n// interpolation keeps each segment monotone between its two points and puts a\n// flat tangent at every local extremum, so the curve never leaves the range of\n// the data. y is treated as a function of x, so the curve never goes backwards.\n//\n// Each cubic segment is converted to an SVG cubic Bezier command.\n// ============================================================================\n\nexport function smoothCurvePath(\n points: Array<{ x: number; y: number }>,\n): string {\n if (points.length === 0) return ''\n if (points.length === 1) return `M${r(points[0]!.x)},${r(points[0]!.y)}`\n if (points.length === 2) {\n return `M${r(points[0]!.x)},${r(points[0]!.y)} L${r(points[1]!.x)},${r(points[1]!.y)}`\n }\n\n const n = points.length\n\n // From here on, n >= 3 (the n <= 2 cases returned above). Every index used\n // below is bound by a `< n - 1` or `< n` loop condition, or is a fixed\n // offset from n itself, into arrays sized exactly `n` or `n - 1`.\n // Non-null assertions on those accesses are the same bounds-checked idiom as\n // loop-index array access elsewhere in this codebase.\n\n // 1. Interval widths and secant slopes\n const h: number[] = []\n const delta: number[] = []\n for (let i = 0; i < n - 1; i++) {\n h.push(points[i + 1]!.x - points[i]!.x)\n delta.push(h[i]! === 0 ? 0 : (points[i + 1]!.y - points[i]!.y) / h[i]!)\n }\n\n // 2. Tangent at each knot (Fritsch-Carlson). Interior: weighted harmonic\n // mean of the adjacent secants, or 0 at a local extremum / flat run.\n const slopes = new Array<number>(n).fill(0)\n for (let i = 1; i < n - 1; i++) {\n const d0 = delta[i - 1]!\n const d1 = delta[i]!\n if (d0 * d1 <= 0) continue\n const w1 = 2 * h[i]! + h[i - 1]!\n const w2 = h[i]! + 2 * h[i - 1]!\n slopes[i] = (w1 + w2) / (w1 / d0 + w2 / d1)\n }\n // Ends: one-sided three-point estimate, clamped so it can't overshoot.\n const endSlope = (h0: number, h1: number, d0: number, d1: number): number => {\n let m = ((2 * h0 + h1) * d0 - h0 * d1) / (h0 + h1)\n if (m * d0 <= 0) m = 0\n else if (d0 * d1 <= 0 && Math.abs(m) > 3 * Math.abs(d0)) m = 3 * d0\n return m\n }\n slopes[0] = endSlope(h[0]!, h[1]!, delta[0]!, delta[1]!)\n slopes[n - 1] = endSlope(h[n - 2]!, h[n - 3]!, delta[n - 2]!, delta[n - 3]!)\n\n // 4. Convert to cubic Bezier — control points strictly between endpoints in x\n let path = `M${r(points[0]!.x)},${r(points[0]!.y)}`\n for (let i = 0; i < n - 1; i++) {\n const seg = h[i]! / 3\n const cp1x = points[i]!.x + seg\n const cp1y = points[i]!.y + slopes[i]! * seg\n const cp2x = points[i + 1]!.x - seg\n const cp2y = points[i + 1]!.y - slopes[i + 1]! * seg\n path += ` C${r(cp1x)},${r(cp1y)} ${r(cp2x)},${r(cp2y)} ${r(points[i + 1]!.x)},${r(points[i + 1]!.y)}`\n }\n\n return path\n}\n\n// ============================================================================\n// Tooltip rendering\n// ============================================================================\n\n/**\n * Multi-value tooltip: category label on top, each series value below with legend text label.\n */\nfunction multiTooltipAbove(\n cx: number,\n topY: number,\n label: string,\n entries: Array<{ text: string; legendLabel: string }>,\n): string {\n const lineH = 20\n const padY = 6\n const labelGap = 10\n const headingW = estimateTextWidth(label, TIP.fontSize, 600)\n const maxRowW = Math.max(\n ...entries.map((e) => {\n const legendW = estimateTextWidth(\n e.legendLabel,\n TIP.fontSize,\n TIP.fontWeight,\n )\n const valW = estimateTextWidth(e.text, TIP.fontSize, TIP.fontWeight)\n return legendW + labelGap + valW\n }),\n )\n const bgW = Math.max(headingW, maxRowW) + TIP.padX * 2\n const bgH = padY + lineH + entries.length * lineH + padY\n\n const tipY = Math.max(TIP.minY, topY - TIP.offsetY - bgH - TIP.pointerSize)\n const bgX = cx - bgW / 2\n\n const ptrX = cx\n const ptrY = tipY + bgH\n const ps = TIP.pointerSize\n const pointer = `<polygon points=\"${r(ptrX - ps)},${r(ptrY)} ${r(ptrX + ps)},${r(ptrY)} ${r(ptrX)},${r(ptrY + ps)}\" class=\"xychart-tip xychart-tip-ptr\"/>`\n\n let svg = `<rect x=\"${r(bgX)}\" y=\"${r(tipY)}\" width=\"${r(bgW)}\" height=\"${bgH}\" rx=\"${TIP.rx}\" class=\"xychart-tip xychart-tip-bg\"/>`\n svg += pointer\n\n // Category label (bold, centered)\n let textY = tipY + padY + lineH / 2\n svg += `<text x=\"${r(cx)}\" y=\"${r(textY)}\" text-anchor=\"middle\" font-weight=\"600\" font-size=\"${TIP.fontSize}\" dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-tip xychart-tip-text\">${escapeXml(label)}</text>`\n\n // Value lines: legend label left-aligned, value right-aligned\n const rowLeft = bgX + TIP.padX\n const rowRight = bgX + bgW - TIP.padX\n for (const entry of entries) {\n textY += lineH\n svg += `<text x=\"${r(rowLeft)}\" y=\"${r(textY)}\" text-anchor=\"start\" font-size=\"${TIP.fontSize}\" font-weight=\"${TIP.fontWeight}\" dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-tip xychart-tip-text\">${escapeXml(entry.legendLabel)}</text>`\n svg += `<text x=\"${r(rowRight)}\" y=\"${r(textY)}\" text-anchor=\"end\" font-size=\"${TIP.fontSize}\" font-weight=\"${TIP.fontWeight}\" dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-tip xychart-tip-text\">${escapeXml(entry.text)}</text>`\n }\n\n return svg\n}\n\nfunction tooltipAbove(cx: number, topY: number, text: string): string {\n const textW = estimateTextWidth(text, TIP.fontSize, TIP.fontWeight)\n const bgW = textW + TIP.padX * 2\n const bgH = TIP.height\n const tipY = Math.max(TIP.minY, topY - TIP.offsetY - bgH - TIP.pointerSize)\n const bgX = cx - bgW / 2\n const textX = cx\n const textY = tipY + bgH / 2\n\n const ptrX = cx\n const ptrY = tipY + bgH\n const ps = TIP.pointerSize\n const pointer = `<polygon points=\"${r(ptrX - ps)},${r(ptrY)} ${r(ptrX + ps)},${r(ptrY)} ${r(ptrX)},${r(ptrY + ps)}\" class=\"xychart-tip xychart-tip-ptr\"/>`\n\n return (\n `<rect x=\"${r(bgX)}\" y=\"${r(tipY)}\" width=\"${r(bgW)}\" height=\"${bgH}\" rx=\"${TIP.rx}\" class=\"xychart-tip xychart-tip-bg\"/>` +\n pointer +\n `<text x=\"${r(textX)}\" y=\"${r(textY)}\" text-anchor=\"middle\" dy=\"${TEXT_BASELINE_SHIFT}\" class=\"xychart-tip xychart-tip-text\">${escapeXml(text)}</text>`\n )\n}\n\nfunction formatTipValue(v: number): string {\n if (Number.isInteger(v)) return v.toLocaleString('en-US')\n return v.toFixed(Math.abs(v) < 10 ? 1 : 0)\n}\n\nfunction r(n: number): string {\n return String(Math.round(n * 10) / 10)\n}\n\nfunction escapeXml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n","import type { C4Element } from '@zombie-mermaid/mermaid-parser'\nimport { estimateTextWidth } from '../styles.ts'\n\n// ============================================================================\n// C4 SVG geometry, shared by the layout (which sizes and places shapes) and\n// the renderer (which draws text and glyphs inside them), so the two can't\n// drift.\n//\n// The numbers follow Mermaid 11.17's C4 renderer: its default `c4` config for\n// spacing, and sizes fitted to what it draws for each shape (a fixed 216px\n// width, 20px padding, a 14px name, a 0.75em type line and a 0.82em\n// description). Text is measured with this package's own width estimate\n// instead of the browser, so sizes agree with Mermaid's to within a few pixels.\n// ============================================================================\n\nexport const C4 = {\n // Mermaid's default `c4` config.\n width: 216,\n diagramMarginX: 50,\n diagramMarginY: 10,\n shapeMargin: 50,\n shapePadding: 20,\n shapeInRow: 4,\n boundaryInRow: 2,\n nextLinePaddingX: 0,\n /**\n * Mermaid limits a row to the browser's `screen.availWidth`, which a server\n * cannot know; a common desktop width keeps the output stable.\n */\n screenWidth: 1440,\n // Text.\n nameSize: 14,\n typeSize: 10.5,\n descrSize: 11.5,\n messageSize: 12,\n boundarySize: 14,\n // Line heights fitted to Mermaid: one more name line adds 15.35px, and a\n // name plus its type line make 31.07px; each description line adds 17.1px.\n nameLine: 15.35,\n typeLine: 15.72,\n descrLine: 17.1,\n /** Each wrapped description line after the first (one `1.1em` row). */\n descrExtraLine: 12.48,\n /**\n * Mermaid's text is about 8% narrower than this package's width estimate\n * (its font is Open Sans, this package's is Inter); fitted on a measured\n * name.\n */\n textScale: 0.922,\n // Heights Mermaid measures for a boundary's heading text (fitted): the\n // title at 16px bold, the `[type]` at 14px, a description at 12px.\n boundaryLabelHeight: 18,\n boundaryTypeHeight: 16.1,\n boundaryDescrHeight: 14.4,\n /** Height Mermaid reserves for the unnamed root boundary's heading. */\n rootHeadingHeight: 47,\n /** Extra height added to a diagram with a title (Mermaid's `extraVertForTitle`). */\n titleExtra: 60,\n // Shapes.\n cornerRadius: 12,\n strokeWidth: 2,\n /**\n * Person proportions at the standard width (Mermaid scales the pill's\n * corners and head with the width, and the head's rise a little faster).\n */\n personRx: 38.232,\n personHeadRadius: 49.68,\n /** Distance from the top of a person to the top of its pill (under the head). */\n personPillTop: 85.946,\n personPillTopPerExtraWidth: 0.4,\n /** Cylinder height lost where its text block meets the lower ellipse. */\n dbTrim: 3.91,\n /** Queue height over its text block. */\n queueExtra: 10,\n} as const\n\n/**\n * Width the text of an element wraps to. Fitted to Mermaid: a 173px name\n * stays on one line and a 231px one wraps, and a 188px name stays on one.\n */\nexport const C4_TEXT_WIDTH = C4.width - C4.shapePadding\n\n/** Greedy word wrap by estimated pixel width. */\nexport function wrapToWidth(\n text: string,\n maxWidth: number,\n fontSize: number,\n weight: number,\n): string[] {\n const lines: string[] = []\n for (const paragraph of text.split('\\n')) {\n let cur = ''\n for (const word of paragraph.split(/\\s+/).filter(Boolean)) {\n const next = cur ? `${cur} ${word}` : word\n if (\n cur &&\n estimateTextWidth(next, fontSize, weight) * C4.textScale > maxWidth\n ) {\n lines.push(cur)\n cur = word\n } else {\n cur = next\n }\n }\n lines.push(cur)\n }\n return lines\n}\n\n/** A person's pill corner, head radius and pill top for a box of this width. */\nexport function c4PersonGeometry(width: number): {\n rx: number\n headRadius: number\n pillTop: number\n} {\n const scale = width / C4.width\n return {\n rx: C4.personRx * scale,\n headRadius: C4.personHeadRadius * scale,\n pillTop:\n C4.personPillTop + C4.personPillTopPerExtraWidth * (width - C4.width),\n }\n}\n\nexport interface C4ShapeSize {\n width: number\n height: number\n /** Height of the text block (name, type, description lines). */\n textHeight: number\n}\n\n/** Height of an element's text block for its wrapped lines. */\nexport function c4TextBlockHeight(\n nameLines: number,\n descriptionLines: number,\n): number {\n return (\n nameLines * C4.nameLine +\n C4.typeLine +\n c4DescriptionHeight(descriptionLines)\n )\n}\n\n/** Height of a description wrapped to this many lines. */\nexport function c4DescriptionHeight(lines: number): number {\n return lines === 0 ? 0 : C4.descrLine + (lines - 1) * C4.descrExtraLine\n}\n\n/** Cylinder cap radii for a box of this width: `rx` across, `ry` down. */\nexport function c4CylinderCap(width: number): { rx: number; ry: number } {\n const rx = width / 2\n return { rx, ry: rx / (2.5 + width / 50) }\n}\n\n/** Queue cap radii for a pipe of this height: `rx` across, `ry` down. */\nexport function c4QueueCap(height: number): { rx: number; ry: number } {\n const ry = height / 2\n return { rx: ry / (2.5 + height / 50), ry }\n}\n\n/** Estimated width of a text line as Mermaid would measure it. */\nexport function c4TextWidth(\n text: string,\n fontSize: number,\n weight: number,\n): number {\n return estimateTextWidth(text, fontSize, weight) * C4.textScale\n}\n\n/**\n * The size Mermaid gives an element: the standard width unless its text is\n * wider (then the text plus padding both sides), and a height that follows\n * its wrapped text lines.\n */\nexport function c4ShapeSize(\n el: Pick<C4Element, 'kind' | 'shape'>,\n nameLines: number,\n descriptionLines: number,\n textWidth: number,\n): C4ShapeSize {\n const textHeight = c4TextBlockHeight(nameLines, descriptionLines)\n const boxHeight = textHeight + 2 * C4.shapePadding\n const width = Math.max(C4.width, textWidth + 2 * C4.shapePadding)\n if (el.kind === 'person') {\n return {\n width,\n // The head rises above the pill, which is as tall as the text needs.\n height: c4PersonGeometry(width).pillTop + boxHeight,\n textHeight,\n }\n }\n if (el.shape === 'db') {\n const { ry } = c4CylinderCap(width)\n return { width, height: boxHeight + 2 * ry - C4.dbTrim, textHeight }\n }\n if (el.shape === 'queue') {\n const height = textHeight + C4.queueExtra\n const { rx } = c4QueueCap(height)\n return { width: width + 3 * rx, height, textHeight }\n }\n return { width, height: boxHeight, textHeight }\n}\n\n/** Mermaid's C4 colours by kind and externality. */\nexport interface C4Palette {\n fill: string\n stroke: string\n text: string\n}\n\nexport function c4Palette(el: Pick<C4Element, 'kind' | 'external'>): C4Palette {\n const text = '#ffffff'\n if (el.external) {\n switch (el.kind) {\n case 'person':\n return { fill: '#686868', stroke: '#8a8a8a', text }\n case 'system':\n return { fill: '#999999', stroke: '#8a8a8a', text }\n case 'container':\n return { fill: '#b3b3b3', stroke: '#a6a6a6', text }\n case 'component':\n return { fill: '#cccccc', stroke: '#bfbfbf', text }\n }\n }\n switch (el.kind) {\n case 'person':\n return { fill: '#08427b', stroke: '#073b6f', text }\n case 'system':\n return { fill: '#1168bd', stroke: '#3c7fc0', text }\n case 'container':\n return { fill: '#438dd5', stroke: '#3c7fc0', text }\n case 'component':\n return { fill: '#85bbf0', stroke: '#78a8d8', text }\n }\n}\n","import type {\n C4Boundary,\n C4Diagram,\n C4Element,\n C4Relationship,\n PositionedC4Boundary,\n PositionedC4Diagram,\n PositionedC4Element,\n PositionedC4Relationship,\n} from '@zombie-mermaid/mermaid-parser'\nimport {\n c4BoundaryTypeLine,\n c4RelLabelLines,\n c4TypeLine,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { Point, RenderOptions } from '@zombie-mermaid/core'\nimport {\n C4,\n C4_TEXT_WIDTH,\n c4PersonGeometry,\n c4ShapeSize,\n c4TextWidth,\n wrapToWidth,\n} from './metrics.ts'\n\n// ============================================================================\n// C4 layout, following Mermaid's own C4 renderer.\n//\n// Mermaid does not run a graph layout for C4. It places shapes in rows, in\n// the order they are declared: up to `c4ShapeInRow` per row, boundaries\n// `c4BoundaryInRow` to a row, each boundary wide enough for what it holds.\n// Relationships are drawn afterwards between the shapes and never move\n// anything, so `Rel_U/D/L/R` (and a render direction) have no effect on\n// placement, as in Mermaid. This is a port of that algorithm\n// (packages/mermaid/src/diagrams/c4/c4Renderer.ts), with text measured by\n// estimate instead of in a browser.\n// ============================================================================\n\n/** Mermaid's `Bounds`: the extent of what has been placed in one boundary. */\ninterface Bounds {\n startx?: number\n stopx?: number\n starty?: number\n stopy?: number\n widthLimit?: number\n /** Where the next shape would go (Mermaid's `nextData`). */\n next: {\n startx?: number\n stopx?: number\n starty?: number\n stopy?: number\n cnt: number\n }\n}\n\nfunction newBounds(): Bounds {\n return { next: { cnt: 0 } }\n}\n\nfunction setData(\n b: Bounds,\n startx: number,\n stopx: number,\n starty: number,\n stopy: number,\n): void {\n b.next.startx = b.startx = startx\n b.next.stopx = b.stopx = stopx\n b.next.starty = b.starty = starty\n b.next.stopy = b.stopy = stopy\n}\n\nfunction grow(\n obj: Bounds | Bounds['next'],\n key: 'startx' | 'stopx' | 'starty' | 'stopy',\n val: number,\n pick: (a: number, b: number) => number,\n): void {\n const cur = obj[key]\n obj[key] = cur === undefined ? val : pick(val, cur)\n}\n\n/** Mermaid's `Bounds.insert`: give a shape the next free slot. */\nfunction insert(b: Bounds, shape: Placed): void {\n b.next.cnt += 1\n const margin = C4.shapeMargin\n let startx =\n b.next.startx === b.next.stopx\n ? b.next.stopx! + margin\n : b.next.stopx! + margin * 2\n let stopx = startx + shape.width\n let starty = b.next.starty! + margin * 2\n let stopy = starty + shape.height\n if (\n startx >= b.widthLimit! ||\n stopx >= b.widthLimit! ||\n b.next.cnt > C4.shapeInRow\n ) {\n startx = b.next.startx! + margin + C4.nextLinePaddingX\n starty = b.next.stopy! + margin * 2\n b.next.stopx = stopx = startx + shape.width\n b.next.starty = b.next.stopy\n b.next.stopy = stopy = starty + shape.height\n b.next.cnt = 1\n }\n shape.x = startx\n shape.y = starty\n grow(b, 'startx', startx, Math.min)\n grow(b, 'starty', starty, Math.min)\n grow(b, 'stopx', stopx, Math.max)\n grow(b, 'stopy', stopy, Math.max)\n grow(b.next, 'startx', startx, Math.min)\n grow(b.next, 'starty', starty, Math.min)\n grow(b.next, 'stopx', stopx, Math.max)\n grow(b.next, 'stopy', stopy, Math.max)\n}\n\ninterface Placed {\n x: number\n y: number\n width: number\n height: number\n}\n\ninterface MeasuredElement extends Placed {\n el: C4Element\n nameLines: string[]\n descriptionLines: string[]\n}\n\nfunction measureElement(el: C4Element): MeasuredElement {\n const nameLines = wrapToWidth(el.label, C4_TEXT_WIDTH, C4.nameSize, 700)\n const descriptionLines = el.description\n ? wrapToWidth(el.description, C4_TEXT_WIDTH, C4.descrSize, 400)\n : []\n const textWidth = Math.max(\n ...nameLines.map((l) => c4TextWidth(l, C4.nameSize, 700)),\n c4TextWidth(c4TypeLine(el), C4.typeSize, 400),\n ...descriptionLines.map((l) => c4TextWidth(l, C4.descrSize, 400)),\n )\n const size = c4ShapeSize(\n el,\n nameLines.length,\n descriptionLines.length,\n textWidth,\n )\n return { el, nameLines, descriptionLines, x: 0, y: 0, ...size }\n}\n\n// A boundary as the layout walks it: its own shapes, then its children.\ninterface Frame {\n alias: string\n boundary?: C4Boundary\n elements: C4Element[]\n children: Frame[]\n}\n\nfunction buildFrames(diagram: C4Diagram): Frame {\n const inBoundary = new Set<string>()\n const byAlias = new Map(diagram.elements.map((e) => [e.alias, e]))\n const frame = (b: C4Boundary): Frame => {\n const elements = b.elementAliases.flatMap((a) => {\n inBoundary.add(a)\n const el = byAlias.get(a)\n return el ? [el] : []\n })\n return {\n alias: b.alias,\n boundary: b,\n elements,\n children: b.children.map(frame),\n }\n }\n const children = diagram.boundaries.map(frame)\n return {\n alias: 'global',\n elements: diagram.elements.filter((e) => !inBoundary.has(e.alias)),\n children,\n }\n}\n\ninterface LaidOutBoundary extends PositionedC4Boundary {\n labelY: number\n}\n\ninterface Layout {\n elements: Map<string, MeasuredElement>\n boundaries: LaidOutBoundary[]\n maxX: number\n maxY: number\n}\n\nfunction layoutFrames(root: Frame): Layout {\n const elements = new Map<string, MeasuredElement>()\n const boundaries: LaidOutBoundary[] = []\n let maxX: number = C4.diagramMarginX\n let maxY: number = C4.diagramMarginY\n\n // Mermaid's `drawC4ShapeArray`: measure, insert each, then bump the margin.\n const placeShapes = (bounds: Bounds, els: C4Element[]): void => {\n for (const el of els) {\n const m = measureElement(el)\n elements.set(el.alias, m)\n insert(bounds, m)\n }\n bounds.stopx! += C4.shapeMargin\n bounds.stopy! += C4.shapeMargin\n }\n\n // Mermaid's `drawInsideBoundary`.\n const inside = (parent: Bounds, frames: Frame[], depth: number): void => {\n const bounds = newBounds()\n bounds.widthLimit =\n parent.widthLimit! / Math.min(C4.boundaryInRow, frames.length)\n for (const [i, fr] of frames.entries()) {\n const b = fr.boundary\n // Height of the boundary's heading, which the shapes start below.\n let Y = 0\n const labelY = Y + 8\n Y = labelY + C4.boundaryLabelHeight\n let typeY: number | undefined\n const typeLine = b ? c4BoundaryTypeLine(b) : undefined\n if (typeLine) {\n typeY = Y + 5\n Y = typeY + C4.boundaryTypeHeight\n }\n let descrY: number | undefined\n if (b?.description) {\n descrY = Y + 20\n Y = descrY + C4.boundaryDescrHeight\n }\n // The unnamed root keeps a fixed heading height.\n if (!b) Y = C4.rootHeadingHeight\n\n if (i === 0 || i % C4.boundaryInRow === 0) {\n const x = parent.startx! + C4.diagramMarginX\n const y = parent.stopy! + C4.diagramMarginY + Y\n setData(bounds, x, x, y, y)\n } else {\n const x =\n bounds.stopx !== bounds.startx\n ? bounds.stopx! + C4.diagramMarginX\n : bounds.startx!\n setData(bounds, x, x, bounds.starty!, bounds.starty!)\n }\n\n if (fr.elements.length > 0) placeShapes(bounds, fr.elements)\n if (fr.children.length > 0) inside(bounds, fr.children, depth + 1)\n\n if (b) {\n let width = bounds.stopx! - bounds.startx!\n let height = bounds.stopy! - bounds.starty!\n // A boundary with nothing in it still gets a visible frame.\n if (width === 0) width = C4.width\n if (height === 0) height = Y + C4.shapeMargin\n boundaries.push({\n alias: b.alias,\n label: b.label,\n ...(b.type ? { type: b.type } : {}),\n ...(b.description ? { description: b.description } : {}),\n x: bounds.startx!,\n y: bounds.starty!,\n width,\n height,\n depth,\n labelY,\n ...(typeY === undefined ? {} : { typeY }),\n ...(descrY === undefined ? {} : { descrY }),\n })\n }\n parent.stopy = Math.max(bounds.stopy! + C4.shapeMargin, parent.stopy!)\n parent.stopx = Math.max(bounds.stopx! + C4.shapeMargin, parent.stopx!)\n maxX = Math.max(maxX, parent.stopx)\n maxY = Math.max(maxY, parent.stopy)\n }\n }\n\n const screen = newBounds()\n setData(\n screen,\n C4.diagramMarginX,\n C4.diagramMarginX,\n C4.diagramMarginY,\n C4.diagramMarginY,\n )\n screen.widthLimit = C4.screenWidth\n // The unnamed root is not drawn, so its children are the depth-0 boundaries.\n inside(screen, [root], -1)\n return { elements, boundaries, maxX, maxY }\n}\n\n/** Where the line from a box's centre toward `toward` leaves the box. */\nfunction rectIntersect(box: Placed, toward: Point): Point {\n const cx = box.x + box.width / 2\n const cy = box.y + box.height / 2\n const dx = toward.x - cx\n const dy = toward.y - cy\n const w = box.width / 2\n const h = box.height / 2\n if (dx === 0 && dy === 0) return { x: cx, y: cy }\n let sx: number\n let sy: number\n if (Math.abs(dy) * w > Math.abs(dx) * h) {\n const half = dy < 0 ? -h : h\n sx = (half * dx) / dy\n sy = half\n } else {\n const half = dx < 0 ? -w : w\n sx = half\n sy = (half * dy) / dx\n }\n return { x: cx + sx, y: cy + sy }\n}\n\n/**\n * Where the line from a person's centre toward `toward` leaves the figure,\n * which is a round head over a pill, not the box around both (Mermaid clips\n * a relationship against what is drawn).\n */\nfunction personIntersect(box: Placed, toward: Point): Point {\n const g = c4PersonGeometry(box.width)\n const cx = box.x + box.width / 2\n const cy = box.y + box.height / 2\n const headY = box.y + g.headRadius\n const pillTop = box.y + g.pillTop\n const pillBottom = box.y + box.height\n const inside = (x: number, y: number): boolean => {\n if (Math.hypot(x - cx, y - headY) <= g.headRadius) return true\n if (y < pillTop || y > pillBottom) return false\n // Round the pill's ends: each is a circle of radius `rx` (clamped).\n const r = Math.min(g.rx, (pillBottom - pillTop) / 2)\n const left = box.x + r\n const right = box.x + box.width - r\n const nearY = Math.min(Math.max(y, pillTop + r), pillBottom - r)\n const nearX = Math.min(Math.max(x, left), right)\n return (\n x >= box.x &&\n x <= box.x + box.width &&\n Math.hypot(x - nearX, y - nearY) <= r\n )\n }\n const len = Math.hypot(toward.x - cx, toward.y - cy)\n if (len === 0 || !inside(cx, cy)) return rectIntersect(box, toward)\n const ux = (toward.x - cx) / len\n const uy = (toward.y - cy) / len\n const at = (t: number): boolean => inside(cx + ux * t, cy + uy * t)\n let lo = 0\n let hi = 0.5\n while (at(hi) && hi < box.width + box.height) {\n lo = hi\n hi += 0.5\n }\n for (let i = 0; i < 30; i++) {\n const mid = (lo + hi) / 2\n if (at(mid)) lo = mid\n else hi = mid\n }\n return { x: cx + ux * lo, y: cy + uy * lo }\n}\n\nfunction leave(box: Placed, toward: Point): Point {\n return (box as Partial<MeasuredElement>).el?.kind === 'person'\n ? personIntersect(box, toward)\n : rectIntersect(box, toward)\n}\n\nconst centre = (b: Placed): Point => ({\n x: b.x + b.width / 2,\n y: b.y + b.height / 2,\n})\n\nfunction routeRelationship(\n rel: C4Relationship,\n index: number,\n boxes: Map<string, Placed>,\n shift: number,\n): PositionedC4Relationship {\n const a = boxes.get(rel.from)!\n const b = boxes.get(rel.to)!\n const start = leave(a, centre(b))\n const end = leave(b, centre(a))\n const dx = end.x - start.x\n const dy = end.y - start.y\n const shifted = (p: Point): Point => ({ x: p.x, y: p.y + shift })\n const out: PositionedC4Relationship = {\n ...rel,\n points: [shifted(start), shifted(end)],\n }\n // Mermaid draws the first relationship straight and every other one as a\n // quadratic curve bent toward the start of the chord.\n if (index > 0) {\n out.curve = shifted({\n x: start.x + dx / 2 - dx / 4,\n y: start.y + dy / 2,\n })\n }\n if (c4RelLabelLines(rel).length > 0) {\n const mid = {\n x: Math.min(start.x, end.x) + Math.abs(dx) / 2,\n y: Math.min(start.y, end.y) + Math.abs(dy) / 2,\n }\n // Mermaid hands its text routine the chord midpoint as the block's left\n // edge and centres the text in the block's width, so a label starts at\n // the midpoint and runs along the line rather than sitting on it.\n const head = c4RelLabelLines(rel)[0]\n const hasHead = rel.label !== '' || rel.index !== undefined\n const labelWidth = hasHead\n ? c4TextWidth(head ?? '', C4.messageSize, 400)\n : 0\n out.labelPosition = shifted({ x: mid.x + labelWidth / 2, y: mid.y })\n if (rel.technology) {\n const techWidth = c4TextWidth(`[${rel.technology}]`, C4.messageSize, 400)\n out.technologyX = mid.x + Math.max(labelWidth, techWidth) / 2\n }\n }\n return out\n}\n\n/** Lay out a parsed C4 diagram the way Mermaid's C4 renderer does. */\nexport function layoutC4DiagramSync(\n diagram: C4Diagram,\n _options: RenderOptions = {},\n): PositionedC4Diagram {\n if (diagram.elements.length === 0 && diagram.boundaries.length === 0) {\n const empty: PositionedC4Diagram = {\n variant: diagram.variant,\n width: 0,\n height: 0,\n elements: [],\n boundaries: [],\n relationships: [],\n }\n if (diagram.title) empty.title = diagram.title\n return empty\n }\n\n const layout = layoutFrames(buildFrames(diagram))\n const titleExtra = diagram.title ? C4.titleExtra : 0\n // Mermaid's viewBox starts `diagramMarginY + titleExtra` above the content.\n const shift = C4.diagramMarginY + titleExtra\n\n const elements: PositionedC4Element[] = diagram.elements.flatMap((el) => {\n const m = layout.elements.get(el.alias)\n if (!m) return []\n return [\n {\n ...el,\n x: m.x,\n y: m.y + shift,\n width: m.width,\n height: m.height,\n nameLines: m.nameLines,\n descriptionLines: m.descriptionLines,\n },\n ]\n })\n\n // Outer boundaries first, so inner ones paint over them.\n const boundaries = [...layout.boundaries]\n .sort((p, q) => p.depth - q.depth)\n .map((b) => ({ ...b, y: b.y + shift }))\n\n const boxes = new Map<string, Placed>()\n for (const m of layout.elements.values()) boxes.set(m.el.alias, m)\n for (const b of layout.boundaries) boxes.set(b.alias, b)\n const relationships = diagram.relationships.map((r, i) =>\n routeRelationship(r, i, boxes, shift),\n )\n\n const width = layout.maxX + C4.diagramMarginX\n const height = layout.maxY + C4.diagramMarginY + titleExtra\n const positioned: PositionedC4Diagram = {\n variant: diagram.variant,\n width,\n height,\n elements,\n boundaries,\n relationships,\n }\n if (diagram.title) {\n positioned.title = diagram.title\n // Mermaid starts the title at `boxWidth / 2 - 4 * marginX`.\n const boxWidth = layout.maxX - C4.diagramMarginX\n positioned.titlePosition = {\n x: boxWidth / 2 - 4 * C4.diagramMarginX,\n y: C4.diagramMarginY + C4.diagramMarginY + shift,\n }\n }\n return positioned\n}\n","import type {\n PositionedC4Boundary,\n PositionedC4Diagram,\n PositionedC4Element,\n PositionedC4Relationship,\n} from '@zombie-mermaid/mermaid-parser'\nimport {\n c4BoundaryTypeLine,\n c4RelLabelLines,\n c4TypeLine,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { DiagramColors, Point, SvgEmitOptions } from '@zombie-mermaid/core'\nimport {\n svgOpenTag,\n buildStyleBlock,\n escapeXml,\n escapeAttr,\n f,\n} from '@zombie-mermaid/core'\nimport { withDataSrc } from '../renderer.ts'\nimport { FONT_SIZES, TEXT_BASELINE_SHIFT } from '../styles.ts'\nimport type { FontSizes } from '../styles.ts'\nimport {\n C4,\n c4CylinderCap,\n c4DescriptionHeight,\n c4Palette,\n c4PersonGeometry,\n c4QueueCap,\n c4TextBlockHeight,\n} from './metrics.ts'\nimport type { C4Palette } from './metrics.ts'\n\n// ============================================================================\n// C4 diagram SVG renderer, drawn the way Mermaid draws C4.\n//\n// Render order (as in Mermaid): boundaries, elements, then relationships,\n// each with its label on top. Elements use Mermaid's C4 colours and stay\n// fixed across themes, as C4 colours carry meaning; boundaries, lines and\n// labels follow the theme's CSS variables.\n// ============================================================================\n\n/**\n * Render a positioned C4 diagram as an SVG string. Parameters mirror\n * `renderErSvg`; see there for the `embedSource` / `title` / `decorative` /\n * `emit` contract.\n */\nexport function renderC4Svg(\n diagram: PositionedC4Diagram,\n colors: DiagramColors,\n font: string = 'Inter',\n transparent: boolean = false,\n _fontSizes: FontSizes = FONT_SIZES,\n embedSource?: string,\n title?: string,\n decorative?: boolean,\n emit: SvgEmitOptions = {},\n): string {\n const parts: string[] = []\n parts.push(\n withDataSrc(\n svgOpenTag(\n diagram.width,\n diagram.height,\n colors,\n transparent,\n title ?? diagram.title,\n decorative,\n undefined,\n emit.styleAttribute,\n ),\n embedSource,\n ),\n )\n parts.push(buildStyleBlock(font, false, emit.nonce))\n\n if (diagram.title && diagram.titlePosition) {\n // Mermaid sets the title as plain text at its start point.\n parts.push(\n f`<text class=\"c4-title\" x=\"${diagram.titlePosition.x}\" y=\"${diagram.titlePosition.y}\" dy=\"${TEXT_BASELINE_SHIFT}\" ` +\n f`font-size=\"16\" fill=\"var(--_text)\">${escapeXml(diagram.title)}</text>`,\n )\n }\n\n for (const b of diagram.boundaries) parts.push(renderBoundary(b))\n for (const el of diagram.elements) parts.push(renderElement(el))\n for (const r of diagram.relationships) {\n parts.push(renderRelationshipLine(r))\n parts.push(renderRelationshipLabel(r))\n }\n\n parts.push('</svg>')\n return parts.filter(Boolean).join('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// Boundaries\n// ---------------------------------------------------------------------------\n\n/** One centred line of text. */\nfunction centred(\n text: string,\n x: number,\n y: number,\n size: number,\n extra: string,\n): string {\n return (\n f` <text x=\"${x}\" y=\"${y}\" text-anchor=\"middle\" dy=\"${TEXT_BASELINE_SHIFT}\" font-size=\"${size}\" ` +\n f`${extra}>${escapeXml(text)}</text>`\n )\n}\n\nfunction renderBoundary(b: PositionedC4Boundary): string {\n const parts: string[] = []\n parts.push(\n f`<g class=\"c4-boundary\" data-id=\"${escapeAttr(b.alias)}\" data-label=\"${escapeAttr(b.label)}\" data-depth=\"${b.depth}\">`,\n )\n // A dashed square-cornered frame with no fill, as Mermaid draws it.\n parts.push(\n f` <rect x=\"${b.x}\" y=\"${b.y}\" width=\"${b.width}\" height=\"${b.height}\" rx=\"2.5\" ry=\"2.5\" ` +\n `fill=\"none\" stroke=\"var(--_text)\" stroke-width=\"1\" stroke-dasharray=\"7,7\" />`,\n )\n const cx = b.x + b.width / 2\n parts.push(\n centred(\n b.label,\n cx,\n b.y + (b.labelY ?? 8),\n C4.boundarySize + 2,\n 'font-weight=\"700\" fill=\"var(--_text)\"',\n ),\n )\n const typeLine = c4BoundaryTypeLine({\n alias: b.alias,\n label: b.label,\n ...(b.type ? { type: b.type } : {}),\n elementAliases: [],\n children: [],\n })\n if (typeLine && b.typeY !== undefined) {\n parts.push(\n centred(\n typeLine,\n cx,\n b.y + b.typeY,\n C4.boundarySize,\n 'fill=\"var(--_text)\"',\n ),\n )\n }\n if (b.description && b.descrY !== undefined) {\n parts.push(\n centred(\n b.description,\n cx,\n b.y + b.descrY,\n C4.boundarySize - 2,\n 'fill=\"var(--_text)\"',\n ),\n )\n }\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// Elements\n// ---------------------------------------------------------------------------\n\nfunction renderElement(el: PositionedC4Element): string {\n const pal = c4Palette(el)\n const { x, y, width: w, height: h } = el\n const cx = x + w / 2\n const textH = c4TextBlockHeight(\n el.nameLines.length,\n el.descriptionLines.length,\n )\n const parts: string[] = []\n parts.push(\n f`<g class=\"c4-element\" data-id=\"${escapeAttr(el.alias)}\" data-kind=\"${el.kind}\" data-shape=\"${el.shape}\" ` +\n f`data-external=\"${el.external}\" data-label=\"${escapeAttr(el.label)}\">`,\n )\n\n // Where the text block starts, and the x it is centred on.\n let top: number\n let textCx = cx\n if (el.kind === 'person') {\n parts.push(...personShape(el, pal))\n const g = c4PersonGeometry(w)\n top = y + g.pillTop + C4.shapePadding\n } else if (el.shape === 'db') {\n parts.push(...cylinderShape(el, pal))\n const { ry } = c4CylinderCap(w)\n // Centred in the body below the upper ellipse.\n top = y + (2 * ry + h) / 2 - textH / 2\n } else if (el.shape === 'queue') {\n parts.push(...queueShape(el, pal))\n const { rx } = c4QueueCap(h)\n // Mermaid centres the text one cap radius left of the pipe's centre.\n textCx = cx - rx\n top = y + h / 2 - textH / 2\n } else {\n parts.push(\n f` <rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" rx=\"${C4.cornerRadius}\" ry=\"${C4.cornerRadius}\" ` +\n f`fill=\"${pal.fill}\" stroke=\"${pal.stroke}\" stroke-width=\"${C4.strokeWidth}\" />`,\n )\n top = y + C4.shapePadding\n }\n\n let cursor = top\n for (const line of el.nameLines) {\n parts.push(\n centred(\n line,\n textCx,\n cursor + C4.nameLine / 2,\n C4.nameSize,\n f`font-weight=\"700\" fill=\"${pal.text}\"`,\n ),\n )\n cursor += C4.nameLine\n }\n parts.push(\n centred(\n c4TypeLine(el),\n textCx,\n cursor + C4.typeLine / 2,\n C4.typeSize,\n f`fill=\"${pal.text}\"`,\n ),\n )\n cursor += C4.typeLine\n el.descriptionLines.forEach((line, i) => {\n // The first line is as tall as Mermaid measures a one-line text; each\n // wrapped line after it is one `1.1em` row.\n parts.push(\n centred(\n line,\n textCx,\n cursor + C4.descrLine / 2 + i * C4.descrExtraLine,\n C4.descrSize,\n f`fill=\"${pal.text}\"`,\n ),\n )\n })\n cursor += c4DescriptionHeight(el.descriptionLines.length)\n parts.push('</g>')\n return parts.join('\\n')\n}\n\n/** A rounded pill with a round head rising out of its top, as Mermaid draws a person. */\nfunction personShape(el: PositionedC4Element, pal: C4Palette): string[] {\n const { x, width: w } = el\n const { rx, headRadius, pillTop: rise } = c4PersonGeometry(w)\n const y = el.y\n const cx = x + w / 2\n const pillTop = y + rise\n const pillHeight = el.y + el.height - pillTop\n const style = f`fill=\"${pal.fill}\" stroke=\"${pal.stroke}\" stroke-width=\"${C4.strokeWidth}\"`\n return [\n f` <rect x=\"${x}\" y=\"${pillTop}\" width=\"${w}\" height=\"${pillHeight}\" rx=\"${rx}\" ry=\"${rx}\" ${style} />`,\n f` <circle cx=\"${cx}\" cy=\"${y + headRadius}\" r=\"${headRadius}\" ${style} />`,\n ]\n}\n\nfunction cylinderShape(el: PositionedC4Element, pal: C4Palette): string[] {\n const { x, y, width: w, height: h } = el\n const { rx, ry } = c4CylinderCap(w)\n const style = f`fill=\"${pal.fill}\" stroke=\"${pal.stroke}\" stroke-width=\"${C4.strokeWidth}\"`\n return [\n f` <path d=\"M ${x} ${y + ry} L ${x} ${y + h - ry} A ${rx} ${ry} 0 0 0 ${x + w} ${y + h - ry} L ${x + w} ${y + ry} Z\" ${style} />`,\n f` <ellipse cx=\"${x + rx}\" cy=\"${y + ry}\" rx=\"${rx}\" ry=\"${ry}\" ${style} />`,\n ]\n}\n\n/**\n * A pipe on its side, as Mermaid draws it: rounded at the left, with the\n * ellipse face showing at the right end.\n */\nfunction queueShape(el: PositionedC4Element, pal: C4Palette): string[] {\n const { x, y, width: w, height: h } = el\n const { rx, ry } = c4QueueCap(h)\n const style = f`fill=\"${pal.fill}\" stroke=\"${pal.stroke}\" stroke-width=\"${C4.strokeWidth}\"`\n return [\n f` <path d=\"M ${x + w - rx} ${y} H ${x + rx} A ${rx} ${ry} 0 0 0 ${x + rx} ${y + h} H ${x + w - rx} Z\" ${style} />`,\n f` <ellipse cx=\"${x + w - rx}\" cy=\"${y + ry}\" rx=\"${rx}\" ry=\"${ry}\" ${style} />`,\n ]\n}\n\n// ---------------------------------------------------------------------------\n// Relationships\n// ---------------------------------------------------------------------------\n\n/** Mermaid's arrowhead: a 10 by 10 triangle whose tip sits on the line's end. */\nfunction arrowHead(tip: Point, from: Point): string {\n const dx = tip.x - from.x\n const dy = tip.y - from.y\n const len = Math.hypot(dx, dy)\n if (len === 0) return ''\n const ux = dx / len\n const uy = dy / len\n const L = 10\n const W = 5\n const bx = tip.x - ux * L\n const by = tip.y - uy * L\n return (\n f`<polygon class=\"c4-arrowhead\" points=\"${tip.x},${tip.y} ${bx - uy * W},${by + ux * W} ${bx + uy * W},${by - ux * W}\" ` +\n `fill=\"var(--_text)\" />`\n )\n}\n\nfunction renderRelationshipLine(rel: PositionedC4Relationship): string {\n const [start, end] = [rel.points[0], rel.points[rel.points.length - 1]]\n if (!start || !end || start === end) return ''\n const attrs = [\n 'class=\"c4-relationship\"',\n f`data-from=\"${escapeAttr(rel.from)}\"`,\n f`data-to=\"${escapeAttr(rel.to)}\"`,\n ]\n if (rel.label) attrs.push(f`data-label=\"${escapeAttr(rel.label)}\"`)\n if (rel.technology) {\n attrs.push(f`data-technology=\"${escapeAttr(rel.technology)}\"`)\n }\n const d = rel.curve\n ? f`M ${start.x},${start.y} Q ${rel.curve.x},${rel.curve.y} ${end.x},${end.y}`\n : f`M ${start.x},${start.y} L ${end.x},${end.y}`\n const line = f`<path ${attrs.join(' ')} d=\"${d}\" fill=\"none\" stroke=\"var(--_text)\" stroke-width=\"1\" />`\n // A curve leaves and arrives along its control point.\n const beforeEnd = rel.curve ?? start\n const afterStart = rel.curve ?? end\n const endHead = arrowHead(end, beforeEnd)\n const startHead = arrowHead(start, afterStart)\n const heads = rel.bidirectional\n ? [endHead, startHead]\n : [rel.reversed ? startHead : endHead]\n return [line, ...heads].filter(Boolean).join('\\n')\n}\n\n/** Plain text at the middle of the chord: the label, then `[technology]` below. */\nfunction renderRelationshipLabel(rel: PositionedC4Relationship): string {\n const lines = c4RelLabelLines(rel)\n if (lines.length === 0 || !rel.labelPosition) return ''\n const { x, y } = rel.labelPosition\n const parts = [\n f`<g class=\"c4-relationship-label\" data-from=\"${escapeAttr(rel.from)}\" data-to=\"${escapeAttr(rel.to)}\">`,\n ]\n lines.forEach((line, i) => {\n const isTech = rel.technology !== undefined && i === lines.length - 1\n // Mermaid puts the technology one font size plus 5px below the label.\n const cy = y + (i === 0 ? 0 : C4.messageSize + 5)\n parts.push(\n centred(\n line,\n isTech && rel.technologyX !== undefined ? rel.technologyX : x,\n cy,\n C4.messageSize,\n isTech\n ? 'font-style=\"italic\" fill=\"var(--_text)\"'\n : 'fill=\"var(--_text)\"',\n ),\n )\n })\n parts.push('</g>')\n return parts.join('\\n')\n}\n","import type {\n MermaidGraph,\n MermaidNode,\n MermaidSubgraph,\n NodeShape,\n EdgeStyle,\n Statement,\n} from '@zombie-mermaid/core'\nimport {\n toDirection,\n normalizeBrTags,\n splitStatementsByLine,\n extractInitConfig,\n applyClickStatement as applyClickStatementShared,\n parseStyleProps,\n tryApplyClassDef,\n tryApplyClassAssignment,\n tryApplyStyleStatement,\n} from '@zombie-mermaid/core'\n\nimport {\n matchExpandedBlock,\n parseExpandedMeta,\n resolveShapeName,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { ExpandedNodeMeta } from '@zombie-mermaid/mermaid-parser'\n/** Remove a single layer of matching wrapping quotes (`\"…\"` or `'…'`). */\nfunction stripWrappingQuotes(s: string): string {\n const t = s.trim()\n if (\n t.length >= 2 &&\n ((t[0] === '\"' && t[t.length - 1] === '\"') ||\n (t[0] === \"'\" && t[t.length - 1] === \"'\"))\n ) {\n return t.slice(1, -1)\n }\n return t\n}\n\n// ============================================================================\n// Mermaid parser — flowcharts and state diagrams\n//\n// Supports:\n// Flowcharts: graph TD / flowchart LR\n// State diagrams: stateDiagram-v2\n//\n// Line-by-line regex approach — the grammar is regular enough\n// that we don't need a grammar generator or full parser combinator.\n// ============================================================================\n\n// `toDirection` used to be defined here, with this file as its only caller.\n// #624 gave it a second caller — `packages/mermaid-parser/src/er/parser.ts`,\n// which cannot import this umbrella file without creating a cycle\n// (mermaid-parser -> umbrella -> mermaid-parser) — so it moved to\n// `packages/core/src/direction.ts` alongside `isDirection`, for the same\n// reason that one moved under #625. Re-exported here so existing importers\n// of `toDirection` from `./parser.ts` (this file's own tests included) keep\n// working unchanged.\nexport { toDirection } from '@zombie-mermaid/core'\n\n/**\n * All diagram-type headers this library recognizes, for the \"supported\n * headers\" list in the invalid-header error below. Kept in one place so a\n * newly-added diagram type doesn't get forgotten in the error message the\n * way sequence/class/ER/xychart were before issue #541.\n */\nconst SUPPORTED_HEADERS =\n '\"graph <dir>\"/\"flowchart <dir>\" (dir: TD, TB, LR, BT, RL), \"stateDiagram-v2\", \"sequenceDiagram\", \"classDiagram\", \"erDiagram\", \"xychart-beta\", \"C4Context\"/\"C4Container\"/\"C4Component\"/\"C4Dynamic\"/\"C4Deployment\", \"architecture-beta\"'\n\n/**\n * Best-guess canonical header for a header line that looks like an attempt\n * at one of the *other* diagram types this library supports, but didn't\n * match `detectDiagramType`'s exact-match regex (a typo, extra trailing\n * text, wrong case pattern, etc.) and so fell through to this flowchart\n * parser as the default. Returns undefined when `header` doesn't look like\n * any of those — in which case the generic \"supported headers\" list in the\n * thrown error is the best we can do.\n *\n * This only recognizes the four *other* multi-word headers\n * (sequenceDiagram/classDiagram/erDiagram/xychart-beta/stateDiagram-v2);\n * \"graph\"/\"flowchart\" typos are handled by the direction-specific message\n * below instead, since those already matched the diagram-type keyword and\n * just have a bad or missing direction token.\n */\nfunction suggestedHeaderFor(header: string): string | undefined {\n const lower = header.trim().toLowerCase()\n if (/^sequence/.test(lower)) return 'sequenceDiagram'\n if (/^class/.test(lower)) return 'classDiagram'\n if (/^er/.test(lower)) return 'erDiagram'\n if (/^xychart/.test(lower)) return 'xychart-beta'\n if (/^state/.test(lower)) return 'stateDiagram-v2'\n return undefined\n}\n\n/**\n * Matches the start of a statement that continues the *previous* one's edge\n * chain rather than beginning a new one: Mermaid lets a vertex-chain\n * statement break across lines, with the link operator leading the next\n * line — e.g.\n *\n * start([Start])\n * ==> green([Change some code])\n * ==> finish([Finish])\n *\n * is one chained statement (`start ==> green ==> finish`), identical to\n * writing it on one line. `splitStatementsByLine` only knows about newlines\n * and `;` as separators, so without this it hands `parseFlowchart` three\n * unrelated-looking statements — the continuation lines start with a bare\n * arrow and no node group, so `parseEdgeLine` can't find a source node and\n * drops them entirely (see issue mermaid-js/mermaid#6049's repro, reported\n * against this parser).\n *\n * Covers every arrow opener `parseEdgeLine` itself recognizes (ARROW_REGEX /\n * TEXT_ARROW_REGEX below): a solid/thick run (`--`, `===`), a dotted run\n * (`-.`, `-.-`), a `~~~` run, each optionally preceded by a `<` marker and/or\n * an edge id (`e1@-->`).\n *\n * Deliberately excludes the `o`/`x` start markers ARROW_REGEX also accepts\n * (`o--o`, `x--x`): unlike `<`, both are also valid bare node ids, so a\n * first-body-line statement like `x-->Y` would otherwise be misread as an\n * `x`-marked continuation of the header and merged into it, corrupting the\n * header line (`flowchart TD x-->Y`) instead of parsing `x` as its own node.\n * A genuine marked-start continuation line is rare enough that losing it is\n * the safer tradeoff.\n */\nconst CONTINUATION_START_REGEX = /^(?:[\\w-]+@)?<?(?:-{2,}|={2,}|-\\.+-?|~{3,})/\n\n/**\n * Rejoin a continuation statement onto the one it continues.\n *\n * `splitStatementsByLine` groups statements by their originating physical\n * line, which is what makes this safe: only the *first* statement in a\n * group is eligible to merge backward into the previous group's last\n * statement (a genuine multi-line continuation, see\n * `CONTINUATION_START_REGEX`). A later statement in the same group got\n * there via an explicit `;` on that line (`A --> B; --> C`) — that boundary\n * must stay a boundary, or `--> C` would wrongly become part of the chain\n * instead of the source-less fragment it actually is.\n */\nfunction mergeContinuationLines(groups: Statement[][]): Statement[] {\n const merged: Statement[] = []\n for (const group of groups) {\n group.forEach((statement, index) => {\n if (\n index === 0 &&\n merged.length > 0 &&\n CONTINUATION_START_REGEX.test(statement.text)\n ) {\n // Keep the *first* line's number: a merged statement is reported at\n // the line its logical statement began, not the continuation line.\n const prev = merged[merged.length - 1]!\n merged[merged.length - 1] = {\n text: `${prev.text} ${statement.text}`,\n line: prev.line,\n }\n } else {\n merged.push(statement)\n }\n })\n }\n return merged\n}\n\n/**\n * Parse Mermaid text into a logical graph structure.\n * Auto-detects diagram type (flowchart or state diagram).\n * Throws on invalid/unsupported input.\n */\nexport function parseMermaid(text: string): MermaidGraph {\n /*\n * Init directives must be read from the raw lines: `%%{init: ...}%%` begins\n * with `%%`, so splitStatementsByLine — which treats `%%` as a comment and\n * truncates the line there — would otherwise discard it along with real\n * comments before it could be seen.\n */\n const initConfig = extractInitConfig(text.split('\\n').map((l) => l.trim()))\n\n const lines = mergeContinuationLines(splitStatementsByLine(text))\n\n if (lines.length === 0) {\n throw new Error('Empty mermaid diagram')\n }\n\n // Detect diagram type from header\n const headerStmt = lines[0]!\n\n // State diagram: \"stateDiagram-v2\" or \"stateDiagram\"\n const graph = /^stateDiagram(-v2)?\\s*$/i.test(headerStmt.text)\n ? parseStateDiagram(lines)\n : parseFlowchart(lines)\n\n graph.initConfig = initConfig\n return graph\n}\n\n// ============================================================================\n// Flowchart parser\n// ============================================================================\n\nfunction parseFlowchart(lines: Statement[]): MermaidGraph {\n // parseFlowchart is only ever invoked by parseMermaid, which has already\n // verified `lines` is non-empty — but that invariant isn't visible to the\n // type checker across the function boundary, so validate it here instead\n // of asserting past it.\n const headerStmt = lines[0]\n if (headerStmt === undefined) {\n /* v8 ignore next */\n throw new Error('parseFlowchart called with no lines')\n }\n const header = headerStmt.text\n\n const headerMatch = header.match(\n /^(?:graph|flowchart)\\s+(TD|TB|LR|BT|RL)\\s*$/i,\n )\n if (!headerMatch) {\n // A header that starts with \"graph\"/\"flowchart\" but has a bad or\n // missing direction gets a targeted message about the direction token\n // specifically — it already picked the right diagram type.\n const partialFlowchartMatch = header.match(\n /^(?:graph|flowchart)\\b\\s*(.*)$/i,\n )\n if (partialFlowchartMatch) {\n const rest = partialFlowchartMatch[1]!.trim()\n throw new Error(\n rest.length > 0\n ? `Line ${headerStmt.line}: Invalid direction \"${rest}\" in header \"${header}\". Expected one of: TD, TB, LR, BT, RL.`\n : `Line ${headerStmt.line}: Missing direction in header \"${header}\". Expected e.g. \"graph TD\" or \"flowchart LR\" — one of: TD, TB, LR, BT, RL.`,\n )\n }\n\n // Otherwise this header didn't match any recognized diagram-type\n // keyword at all (typo, extra text, or genuinely unsupported syntax) —\n // point at the closest known diagram type when the text resembles one.\n const suggestion = suggestedHeaderFor(header)\n const hint =\n suggestion && suggestion.toLowerCase() !== header.trim().toLowerCase()\n ? ` Did you mean \"${suggestion}\"?`\n : ''\n throw new Error(\n `Line ${headerStmt.line}: Invalid mermaid header: \"${header}\".${hint} Supported headers: ${SUPPORTED_HEADERS}.`,\n )\n }\n\n const direction = toDirection(headerMatch[1])\n\n const graph: MermaidGraph = {\n direction,\n nodes: new Map(),\n edges: [],\n subgraphs: [],\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n\n // Subgraph stack for nested subgraphs.\n const subgraphStack: MermaidSubgraph[] = []\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- classDef / class assignment / style — shared with the class-diagram\n // parser, see packages/core/src/style-directives.ts ---\n if (tryApplyClassDef(line, graph)) continue\n if (tryApplyClassAssignment(line, graph)) continue\n if (tryApplyStyleStatement(line, graph)) continue\n\n // --- click interaction: `click A \"url\" \"tooltip\" _blank` / `click A call fn()` ---\n if (/^click\\s+/i.test(line)) {\n applyClickStatement(line, graph)\n continue\n }\n\n // --- edge metadata: `e1@{ animate: true }` — shared with parseStateDiagram ---\n if (tryApplyEdgeMetaLine(line, graph)) {\n continue\n }\n\n // --- linkStyle: `linkStyle 0 stroke:#f00` or `linkStyle default stroke:#f00` ---\n const linkStyleMatch = line.match(/^linkStyle\\s+(default|[\\d,\\s]+)\\s+(.+)$/)\n if (linkStyleMatch) {\n const target = linkStyleMatch[1]!.trim()\n const props = parseStyleProps(linkStyleMatch[2]!)\n if (target === 'default') {\n graph.linkStyles.set('default', {\n ...graph.linkStyles.get('default'),\n ...props,\n })\n } else {\n const indices = target.split(',').map((s) => parseInt(s.trim(), 10))\n for (const idx of indices) {\n if (!isNaN(idx)) {\n graph.linkStyles.set(idx, {\n ...graph.linkStyles.get(idx),\n ...props,\n })\n }\n }\n }\n continue\n }\n\n // --- direction override inside subgraph: `direction LR` ---\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch && subgraphStack.length > 0) {\n subgraphStack[subgraphStack.length - 1]!.direction = toDirection(\n dirMatch[1],\n )\n continue\n }\n\n // --- subgraph start: `subgraph Label` or `subgraph id [Label]` ---\n const subgraphMatch = line.match(/^subgraph\\s+(.+)$/)\n if (subgraphMatch) {\n const rest = subgraphMatch[1]!.trim()\n // Check for `subgraph id [Label]` / `subgraph id [\"Label\"]` form. The id\n // may contain non-ASCII chars (e.g. CJK), so match any run of non-space,\n // non-`[` chars rather than ASCII [\\w-]; strip optional quotes wrapping\n // the title.\n const bracketMatch = rest.match(/^([^\\s[]+)\\s*\\[(.+)\\]$/)\n let id: string\n let label: string\n if (bracketMatch) {\n id = bracketMatch[1]!\n label = normalizeBrTags(stripWrappingQuotes(bracketMatch[2]!))\n } else {\n // `subgraph Label` (optionally quoted): the label doubles as the id,\n // slugified — but preserve unicode letters/numbers so a CJK-only title\n // doesn't collapse to an empty id (which broke nested layout).\n label = normalizeBrTags(stripWrappingQuotes(rest))\n id = label.replace(/\\s+/g, '_').replace(/[^\\p{L}\\p{N}_-]/gu, '')\n }\n const sg: MermaidSubgraph = { id, label, nodeIds: [], children: [] }\n subgraphStack.push(sg)\n continue\n }\n\n // --- subgraph end ---\n if (line === 'end') {\n const completed = subgraphStack.pop()\n if (completed) {\n if (subgraphStack.length > 0) {\n subgraphStack[subgraphStack.length - 1]!.children.push(completed)\n } else {\n graph.subgraphs.push(completed)\n }\n }\n continue\n }\n\n // --- Edge/node definitions ---\n parseEdgeLine(line, graph, subgraphStack)\n }\n\n return graph\n}\n\n// ============================================================================\n// State diagram parser\n//\n// Supported syntax:\n// stateDiagram-v2\n// s1 : Description\n// state \"Description\" as s1\n// s1 --> s2 : label\n// [*] --> s1 (start pseudostate)\n// s1 --> [*] (end pseudostate)\n// state CompositeState {\n// inner1 --> inner2\n// }\n// classDef name fill:#f00 (style class — shared helpers)\n// class s1,s2 name\n// s1:::name --> s2 (inline class shorthand)\n// ============================================================================\n\nfunction parseStateDiagram(lines: Statement[]): MermaidGraph {\n const graph: MermaidGraph = {\n direction: 'TD',\n nodes: new Map(),\n edges: [],\n subgraphs: [],\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n\n // Track composite state nesting (like subgraphs)\n const compositeStack: MermaidSubgraph[] = []\n // Track all composite state IDs to avoid creating duplicate nodes\n const compositeStateIds = new Set<string>()\n // Counter for unique [*] pseudostate IDs\n let startCount = 0\n let endCount = 0\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- classDef / class assignment / style — shared with the flowchart and\n // class-diagram parsers, see packages/core/src/style-directives.ts.\n // Mermaid can't style `[*]` or composite states yet, so an assignment to\n // one of those ids is stored but never matches a rendered node. ---\n if (tryApplyClassDef(line, graph)) continue\n if (tryApplyClassAssignment(line, graph)) continue\n if (tryApplyStyleStatement(line, graph)) continue\n\n // --- direction override ---\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch) {\n if (compositeStack.length > 0) {\n compositeStack[compositeStack.length - 1]!.direction = toDirection(\n dirMatch[1],\n )\n } else {\n graph.direction = toDirection(dirMatch[1])\n }\n continue\n }\n\n // --- linkStyle: `linkStyle 0 stroke:#f00` or `linkStyle default stroke:#f00` ---\n const linkStyleMatch = line.match(/^linkStyle\\s+(default|[\\d,\\s]+)\\s+(.+)$/)\n if (linkStyleMatch) {\n const target = linkStyleMatch[1]!.trim()\n const props = parseStyleProps(linkStyleMatch[2]!)\n if (target === 'default') {\n graph.linkStyles.set('default', {\n ...graph.linkStyles.get('default'),\n ...props,\n })\n } else {\n const indices = target.split(',').map((s) => parseInt(s.trim(), 10))\n for (const idx of indices) {\n if (!isNaN(idx)) {\n graph.linkStyles.set(idx, {\n ...graph.linkStyles.get(idx),\n ...props,\n })\n }\n }\n }\n continue\n }\n\n // --- edge metadata: `e1@{ animate: true }` — shared with parseFlowchart ---\n if (tryApplyEdgeMetaLine(line, graph)) {\n continue\n }\n\n // --- composite state start: `state CompositeState {` ---\n const compositeMatch = line.match(\n /^state\\s+(?:\"([^\"]+)\"\\s+as\\s+)?([\\w\\p{L}]+)\\s*\\{$/u,\n )\n if (compositeMatch) {\n const label = compositeMatch[1] ?? compositeMatch[2]!\n const id = compositeMatch[2]!\n const sg: MermaidSubgraph = { id, label, nodeIds: [], children: [] }\n compositeStack.push(sg)\n // Track this ID to avoid creating a duplicate node for the composite state\n compositeStateIds.add(id)\n // Remove any existing node that was created when parsing transitions before\n // this composite state definition (e.g., \"A --> Processing\" before \"state Processing {\")\n graph.nodes.delete(id)\n continue\n }\n\n // --- composite state end ---\n if (line === '}') {\n const completed = compositeStack.pop()\n if (completed) {\n if (compositeStack.length > 0) {\n compositeStack[compositeStack.length - 1]!.children.push(completed)\n } else {\n graph.subgraphs.push(completed)\n }\n }\n continue\n }\n\n // --- state alias: `state \"Description\" as s1` (without brace) ---\n const stateAliasMatch = line.match(\n /^state\\s+\"([^\"]+)\"\\s+as\\s+([\\w\\p{L}]+)\\s*$/u,\n )\n if (stateAliasMatch) {\n const label = normalizeBrTags(stateAliasMatch[1]!)\n const id = stateAliasMatch[2]!\n registerStateDescription(graph, compositeStack, id, label)\n continue\n }\n\n /*\n * --- transition: `s1 --> s2`, `s1 --> s2 : label`, `[*] --> s1`, or\n * with an edge id (Mermaid v11.10.0+): `s1 e1@--> s2` ---\n *\n * State-diagram transitions only ever use `-->` (unlike flowchart's\n * many arrow variants), so the edge id — same `id@` prefix syntax as\n * flowchart's `A e1@--> B` — is captured inline in this one regex\n * rather than needing flowchart's separate pre-arrow scan.\n */\n const transitionMatch = line.match(\n /^(\\[\\*\\]|[\\w\\p{L}-]+)(?::::([\\w][\\w-]*))?\\s*(?:([\\w-]+)@)?-->\\s*(\\[\\*\\]|[\\w\\p{L}-]+)(?::::([\\w][\\w-]*))?(?:\\s*:\\s*(.+))?$/u,\n )\n if (transitionMatch) {\n let sourceId = transitionMatch[1]!\n const sourceClass = transitionMatch[2]\n const edgeId = transitionMatch[3]\n let targetId = transitionMatch[4]!\n const targetClass = transitionMatch[5]\n const rawTransitionLabel = transitionMatch[6]?.trim()\n const edgeLabel = rawTransitionLabel\n ? normalizeBrTags(rawTransitionLabel)\n : undefined\n\n // Handle [*] pseudostates — each occurrence gets a unique ID\n if (sourceId === '[*]') {\n startCount++\n sourceId = `_start${startCount > 1 ? startCount : ''}`\n registerStateNode(graph, compositeStack, {\n id: sourceId,\n label: '',\n shape: 'state-start',\n })\n } else if (!compositeStateIds.has(sourceId)) {\n // Only create a node if this isn't a composite state\n ensureStateNode(graph, compositeStack, sourceId)\n }\n\n if (targetId === '[*]') {\n endCount++\n targetId = `_end${endCount > 1 ? endCount : ''}`\n registerStateNode(graph, compositeStack, {\n id: targetId,\n label: '',\n shape: 'state-end',\n })\n } else if (!compositeStateIds.has(targetId)) {\n // Only create a node if this isn't a composite state\n ensureStateNode(graph, compositeStack, targetId)\n }\n\n // `S1:::name --> S2:::name` — inline class shorthand on either end\n if (sourceClass !== undefined) {\n graph.classAssignments.set(sourceId, sourceClass)\n }\n if (targetClass !== undefined) {\n graph.classAssignments.set(targetId, targetClass)\n }\n\n graph.edges.push({\n source: sourceId,\n target: targetId,\n label: edgeLabel,\n style: 'solid',\n hasArrowStart: false,\n hasArrowEnd: true,\n ...(edgeId !== undefined ? { id: edgeId } : {}),\n })\n continue\n }\n\n // --- bare class shorthand: `S2:::name`. Must precede the description\n // rule below, which would otherwise read it as `S2 : ::name`. ---\n const stateClassMatch = line.match(/^([\\w\\p{L}-]+):::([\\w][\\w-]*)\\s*$/u)\n if (stateClassMatch) {\n const id = stateClassMatch[1]!\n if (!compositeStateIds.has(id)) {\n ensureStateNode(graph, compositeStack, id)\n }\n graph.classAssignments.set(id, stateClassMatch[2]!)\n continue\n }\n\n // --- state description: `s1 : Description` ---\n const stateDescMatch = line.match(/^([\\w\\p{L}-]+)\\s*:\\s*(.+)$/u)\n if (stateDescMatch) {\n const id = stateDescMatch[1]!\n const label = normalizeBrTags(stateDescMatch[2]!.trim())\n registerStateDescription(graph, compositeStack, id, label)\n continue\n }\n }\n\n // `class S2 foo` on a state that no transition mentions still declares it\n // (Mermaid creates the state on first reference). Composite ids are skipped:\n // they render as clusters, not nodes.\n for (const id of graph.classAssignments.keys()) {\n if (!graph.nodes.has(id) && !compositeStateIds.has(id)) {\n ensureStateNode(graph, [], id)\n }\n }\n\n return graph\n}\n\n/**\n * Register a state with an explicit description (`s1 : text` or\n * `state \"text\" as s1`). Unlike a bare reference, this replaces the\n * placeholder label an earlier `s1 --> s2` or `s1:::name` gave the node.\n */\nfunction registerStateDescription(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n id: string,\n label: string,\n): void {\n const existing = graph.nodes.get(id)\n if (existing) existing.label = label\n registerStateNode(graph, compositeStack, { id, label, shape: 'rounded' })\n}\n\n/** Register a state node and track in composite state if applicable */\nfunction registerStateNode(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n node: MermaidNode,\n): void {\n const isNew = !graph.nodes.has(node.id)\n if (isNew) {\n graph.nodes.set(node.id, node)\n }\n if (compositeStack.length > 0) {\n const current = compositeStack[compositeStack.length - 1]!\n if (!current.nodeIds.includes(node.id)) {\n current.nodeIds.push(node.id)\n }\n }\n}\n\n/** Ensure a state node exists with default rounded shape */\nfunction ensureStateNode(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n id: string,\n): void {\n if (!graph.nodes.has(id)) {\n registerStateNode(graph, compositeStack, {\n id,\n label: id,\n shape: 'rounded',\n })\n } else {\n // Track in composite if applicable\n if (compositeStack.length > 0) {\n const current = compositeStack[compositeStack.length - 1]!\n if (!current.nodeIds.includes(id)) {\n current.nodeIds.push(id)\n }\n }\n }\n}\n\n// ============================================================================\n// Flowchart edge line parser\n//\n// Handles chained edges like: A[Label] --> B(Label) -.-> C{Label}\n// Also handles & parallel links: A & B --> C & D\n// ============================================================================\n\n/**\n * Arrow regex — matches all arrow operators with optional labels.\n *\n * Supported operators:\n * --> --- solid arrow / solid line\n * -.-> -.- dotted arrow / dotted line\n * ==> === thick arrow / thick line\n * <--> <-.-> <==> bidirectional variants\n * --o --x circle-end / cross-end arrow (see issue #65)\n * o-- x-- circle-start / cross-start arrow\n * o--o x--x circle/cross at both ends\n *\n * The doubled `o--o`/`x--x` forms must be tried before the single-sided\n * `o--`/`x--` forms — regex alternation picks the first alternative that\n * matches, not the longest, so `o--` listed first would consume just the\n * start marker and strand the trailing `o` as a bogus token. Capturing the\n * markers as their own groups (rather than baking them into an alternation\n * of whole tokens) sidesteps that ordering problem entirely.\n *\n * The body is a variable-length run: Mermaid uses extra characters as a\n * layout-rank hint (`A ---- B` pushes B one rank further than `A --> B`).\n * A fixed alternation of `-->|---|==>` mis-tokenized anything longer,\n * stranding the surplus characters and corrupting the following token.\n * The run length is parsed so the edge survives; the rank hint it encodes\n * is not yet modelled by the layout engine.\n *\n * `~~~` is Mermaid's invisible link: it participates in layout but draws\n * nothing.\n *\n * Optional label: -->|label text|\n */\nconst ARROW_REGEX = /^(<|o|x)?(-{2,}|={2,}|-\\.+-|~{3,})(>|o|x)?(?:\\|([^|]*)\\|)?/\n\n/**\n * Link bodies that are only a link when a start or end marker accompanies\n * them.\n *\n * A bare `--` (or `==`) opens Mermaid's text-embedded label syntax —\n * `A -- Yes --> B` — which TEXT_ARROW_REGEX handles. Treating it as an\n * unmarked open link here would consume the opener and strand the label.\n * Mermaid itself requires three characters for an unmarked open link\n * (`A --- B`), so this only rejects what Mermaid also rejects.\n */\nconst AMBIGUOUS_UNMARKED_BODIES = new Set(['--', '=='])\n\n/**\n * Map a raw start/end marker character (`<`, `>`, `o`, `x`, or undefined) to\n * the distinct terminator shape it draws, if any.\n *\n * `<`/`>` are plain arrowheads — already handled by `hasArrowStart`/\n * `hasArrowEnd` — so they map to `undefined` here, same as no marker at all.\n * Only `o`/`x` need their own shape (circle/cross) recorded, so the ASCII\n * renderer can draw something other than the default arrowhead glyph — see\n * issue #330.\n */\nfunction markerKind(\n marker: string | undefined,\n): 'circle' | 'cross' | undefined {\n if (marker === 'o') return 'circle'\n if (marker === 'x') return 'cross'\n return undefined\n}\n\n/**\n * Text-embedded label regex — matches \"-- label -->\", \"-. label .->\", \"== label ==>\" syntax.\n * Tried as fallback when ARROW_REGEX doesn't match.\n *\n * The closing operator is a variable-length run, matching ARROW_REGEX. While\n * it was a fixed alternation, `A -- label ----> B` consumed only `---` from\n * `---->`, leaving `-> B`, which forms no node group — so the edge and its\n * target node were both dropped silently. Exactly the failure mode the\n * variable-length work exists to remove.\n *\n * Based on PR #36 by @liuxiaopai-ai (https://github.com/lukilabs/beautiful-mermaid/pull/36)\n */\nconst TEXT_ARROW_REGEX =\n /^(<|o|x)?(--|-\\.|==)\\s+(.+?)\\s+(-{2,}[>ox]|={2,}[>ox]|\\.+-[>ox]|-{3,}|={3,}|-\\.+-)/\n\n/**\n * Node shape patterns — ordered from most specific delimiters to least.\n * Multi-char delimiters must be tried before single-char to avoid false matches.\n *\n * The label-content group for each shape is quote-aware: it matches either a\n * complete `\"...\"` quoted span (any character, including this shape's own\n * closing delimiter chars, is fine inside quotes) or a single character that\n * isn't the start of the closing delimiter. This stops the scanner from\n * treating a `]`/`)`/`}` etc. *inside* a quoted label as the node's real\n * closing bracket (see issue #61) — without this, `A[\"test [] brackets\"]`\n * would stop at the first `]`, which lives inside the quoted string.\n */\n/**\n * A node shape pattern.\n *\n * `shape` is usually a fixed `NodeShape`. For the slash-bracket family it is\n * a function instead, because those four shapes are distinguished by which\n * delimiter *closes* them — which the regex has to capture rather than\n * hard-code. See SLASH_BRACKET note below.\n */\ninterface NodePattern {\n regex: RegExp\n shape: NodeShape | ((closingDelimiter: string) => NodeShape)\n}\n\nconst NODE_PATTERNS: NodePattern[] = [\n // Triple delimiters (must be first)\n {\n regex: /^([\\w\\p{L}-]+)\\(\\(\\(((?:\"[^\"]*\"|(?!\\)\\)\\)).)+)\\)\\)\\)/u,\n shape: 'doublecircle',\n }, // A(((text)))\n\n // Double delimiters with mixed brackets\n {\n regex: /^([\\w\\p{L}-]+)\\(\\[((?:\"[^\"]*\"|(?!\\]\\)).)+)\\]\\)/u,\n shape: 'stadium',\n }, // A([text])\n { regex: /^([\\w\\p{L}-]+)\\(\\(((?:\"[^\"]*\"|(?!\\)\\)).)+)\\)\\)/u, shape: 'circle' }, // A((text))\n {\n regex: /^([\\w\\p{L}-]+)\\[\\[((?:\"[^\"]*\"|(?!\\]\\]).)+)\\]\\]/u,\n shape: 'subroutine',\n }, // A[[text]]\n {\n regex: /^([\\w\\p{L}-]+)\\[\\(((?:\"[^\"]*\"|(?!\\)\\]).)+)\\)\\]/u,\n shape: 'cylinder',\n }, // A[(text)]\n\n /*\n * SLASH_BRACKET family — must come before plain [text].\n *\n * Four shapes share a `[` + slash opener and differ only in which\n * delimiter closes them:\n *\n * A[/text\\] trapezoid A[/text/] parallelogram\n * A[\\text/] trapezoid-alt A[\\text\\] parallelogram-alt\n *\n * They CANNOT be four separate patterns each negated against its own\n * closing pair. A pattern for `[/…\\]` whose content merely excludes `\\]`\n * will happily run past a `/]` to reach a `\\]` later on the same line, so\n *\n * A[/parallelogram/] --> B[\\alt\\]\n *\n * matched as a single trapezoid whose label was the entire statement —\n * silently swallowing the edge and the second node. Ordering the patterns\n * differently only moves which input breaks.\n *\n * Instead, one pattern per opener stops at whichever slash-close comes\n * first and captures it, and the captured delimiter selects the shape.\n */\n {\n regex: /^([\\w\\p{L}-]+)\\[\\/((?:\"[^\"]*\"|(?![\\\\/]\\]).)+)([\\\\/])\\]/u,\n shape: (close) => (close === '\\\\' ? 'trapezoid' : 'parallelogram'),\n }, // A[/text\\] or A[/text/]\n {\n regex: /^([\\w\\p{L}-]+)\\[\\\\((?:\"[^\"]*\"|(?![\\\\/]\\]).)+)([\\\\/])\\]/u,\n shape: (close) => (close === '/' ? 'trapezoid-alt' : 'parallelogram-alt'),\n }, // A[\\text/] or A[\\text\\]\n\n // Asymmetric flag shape\n { regex: /^([\\w\\p{L}-]+)>((?:\"[^\"]*\"|(?!\\]).)+)\\]/u, shape: 'asymmetric' }, // A>text]\n\n // Double curly braces (hexagon) — must come before single {text}\n {\n regex: /^([\\w\\p{L}-]+)\\{\\{((?:\"[^\"]*\"|(?!\\}\\}).)+)\\}\\}/u,\n shape: 'hexagon',\n }, // A{{text}}\n\n // Single-char delimiters (last — most common, least specific)\n { regex: /^([\\w\\p{L}-]+)\\[((?:\"[^\"]*\"|(?!\\]).)+)\\]/u, shape: 'rectangle' }, // A[text]\n { regex: /^([\\w\\p{L}-]+)\\(((?:\"[^\"]*\"|(?!\\)).)+)\\)/u, shape: 'rounded' }, // A(text)\n { regex: /^([\\w\\p{L}-]+)\\{((?:\"[^\"]*\"|(?!\\}).)+)\\}/u, shape: 'diamond' }, // A{text}\n]\n\n/**\n * Regex for a bare node reference (just an ID, no shape brackets).\n *\n * Only allows a hyphen when it's sandwiched between word characters\n * (`foo-bar`, `step-1-b`), never a bare/trailing/doubled hyphen. This keeps\n * legitimately hyphenated ids intact while stopping the id from swallowing\n * the leading dashes of an immediately-following arrow when there's no\n * whitespace before it — e.g. `A-->B` (see issue #61): the naive `[\\w-]+`\n * greedily consumed `A--`, leaving a bogus node and no edge. Arrow tokens\n * always start with `-`/`=`/`<` followed by another non-word character\n * (`-`, `.`, `=`, `>`), which this pattern never matches into, so it now\n * stops cleanly at `A` and lets the arrow regex take over.\n *\n * `\\p{L}` (any Unicode letter, alongside plain `\\w`) is included so a bare\n * non-ASCII name (`Lasaña`, `日本`) matches in full instead of truncating at\n * the first non-ASCII character and silently stranding the rest of the line\n * as unparsed text — see issue #328. This mirrors the state-diagram\n * transition regex below, which already allows `\\p{L}` for the same reason.\n */\nconst BARE_NODE_REGEX = /^([\\w\\p{L}]+(?:-[\\w\\p{L}]+)*)/u\n\n/**\n * Node id immediately followed by the expanded-syntax opener: `A@{`.\n *\n * Only the id is captured — the block itself needs depth- and quote-aware\n * scanning (a label may contain `}`), which a regex would do badly, so\n * `matchExpandedBlock` takes over from here.\n */\nconst EXPANDED_NODE_ID_REGEX = /^([\\w\\p{L}-]+)(?=@\\{)/u\n\n/**\n * Resolve the geometry for an `A@{ ... }` node.\n *\n * An `icon:` or `img:` node has no `shape:` of its own — its outline comes\n * from `form:` instead (Mermaid defaults to a square). An unrecognized shape\n * name falls back to a rectangle rather than throwing: Mermaid adds shape\n * names regularly, and rendering a plain box beats failing the whole diagram\n * over one unknown name.\n */\nfunction expandedNodeShape(meta: ExpandedNodeMeta): NodeShape {\n if (meta.shape) {\n return resolveShapeName(meta.shape) ?? 'rectangle'\n }\n\n if (meta.icon !== undefined || meta.img !== undefined) {\n switch (meta.form?.toLowerCase()) {\n case 'circle':\n return 'circle'\n case 'rounded':\n return 'rounded'\n default:\n return 'rectangle'\n }\n }\n\n return 'rectangle'\n}\n\n/**\n * Resolve the display label for an `A@{ ... }` node.\n *\n * Falls back to the node id when no `label:` is given, matching how the\n * bracket syntax treats a bare `A`. For an icon or image node with no label,\n * the icon/image reference itself is shown: this renderer draws neither\n * FontAwesome glyphs nor remote images, so showing the reference is more\n * useful than an empty box, and it keeps the node identifiable.\n */\nfunction expandedNodeLabel(id: string, meta: ExpandedNodeMeta): string {\n if (meta.label !== undefined && meta.label.length > 0) return meta.label\n if (meta.icon) return meta.icon\n if (meta.img) return meta.img\n return id\n}\n\n/**\n * Apply a `click` statement to the graph.\n *\n * Mermaid's forms:\n * click A \"https://example.com\"\n * click A \"https://example.com\" \"Tooltip\"\n * click A \"https://example.com\" _blank\n * click A href \"https://example.com\" \"Tooltip\" _blank\n * click A call myCallback()\n * click A callback \"Tooltip\"\n *\n * A callback is recorded but never invoked — this renderer emits static SVG\n * and does not execute script supplied by a diagram. An href, by contrast, is\n * genuinely actionable: the node is wrapped in an SVG <a>, which works in any\n * browser without script.\n *\n * The actual grammar and href-safety rules live in packages/core/src/click-directive.ts,\n * shared with the class diagram parser\n * (packages/mermaid-parser/src/class/parser.ts) — this is a thin wrapper\n * binding it to this parser's `graph.interactions` map.\n */\nfunction applyClickStatement(line: string, graph: MermaidGraph): void {\n applyClickStatementShared(line, graph.interactions)\n}\n\n/**\n * Id immediately followed by the expanded-syntax opener on a standalone\n * metadata line: `e1@{ ... }`.\n *\n * Shared by both parsers (`tryApplyEdgeMetaLine`) since the syntax and its\n * \"must already be a known edge id\" disambiguation rule are identical for\n * flowcharts and state diagrams — only how edge ids get declared differs\n * (`A e1@--> B` vs. `A e1@--> B` with a simpler arrow set).\n */\nconst EDGE_META_ID_REGEX = /^([\\w-]+)(?=@\\{)/\n\n/**\n * Try to parse and apply a standalone `e1@{ animate: true }` edge-metadata\n * line.\n *\n * Told apart from a node's `A@{ shape: ... }` by whether the id was already\n * declared as an edge id (`A e1@--> B`) — checked before node parsing so an\n * edge id is never registered as a stray node. Shared between\n * `parseFlowchart` and `parseStateDiagram`; returns `true` if the line was\n * consumed as edge metadata (caller should `continue` its loop).\n */\nfunction tryApplyEdgeMetaLine(line: string, graph: MermaidGraph): boolean {\n const edgeMetaMatch = line.match(EDGE_META_ID_REGEX)\n if (!edgeMetaMatch || !graph.edges.some((e) => e.id === edgeMetaMatch[1])) {\n return false\n }\n const block = matchExpandedBlock(line.slice(edgeMetaMatch[1]!.length))\n if (!block) return false\n applyEdgeMeta(graph, edgeMetaMatch[1]!, parseExpandedMeta(block.body))\n return true\n}\n\n/**\n * Apply an `e1@{ ... }` metadata block to the edge with that id.\n *\n * Only `animate` is acted on; Mermaid's other edge keys (`animation`,\n * `curve`) are recorded as no-ops rather than errors, matching how an\n * unknown node shape degrades.\n */\nfunction applyEdgeMeta(\n graph: MermaidGraph,\n edgeId: string,\n meta: Record<string, string | undefined>,\n): void {\n for (const edge of graph.edges) {\n if (edge.id !== edgeId) continue\n if (meta.animate !== undefined) {\n edge.animate = meta.animate.toLowerCase() !== 'false'\n }\n // Mermaid's `animation: fast|slow` is a speed hint on top of animate.\n if (meta.animation !== undefined) edge.animate = true\n }\n}\n\n/** Regex for ::: class shorthand suffix — matches :::className immediately after a node */\nconst CLASS_SHORTHAND_REGEX = /^:::([\\w][\\w-]*)/\n\n/**\n * Regex for ::: class shorthand appearing BEFORE the shape brackets,\n * e.g. A:::external[Label]. Captures the bare id and the class name so\n * the shorthand can be stripped out before shape-pattern matching runs.\n *\n * The id group allows `\\p{L}` (see BARE_NODE_REGEX above) so a non-ASCII\n * id keeps working with this shorthand instead of falling through to\n * BARE_NODE_REGEX with the `:::className` suffix left dangling as\n * unparsed text. The class-name group stays ASCII-only — Mermaid class\n * names follow CSS identifier conventions, not node-name conventions.\n */\nconst PRE_CLASS_SHORTHAND_REGEX = /^([\\w\\p{L}-]+):::([\\w][\\w-]*)/u\n\n/**\n * Parse a line that contains node definitions and edges.\n * Handles chaining: A --> B --> C produces edges A→B and B→C.\n * Handles parallel links: A & B --> C & D produces 4 edges.\n */\nfunction parseEdgeLine(\n line: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): void {\n let remaining = line.trim()\n\n // Parse the first node group (possibly with & separators)\n const firstGroup = consumeNodeGroup(remaining, graph, subgraphStack)\n if (!firstGroup || firstGroup.ids.length === 0) return\n\n remaining = firstGroup.remaining.trim()\n let prevGroupIds = firstGroup.ids\n\n // Parse arrow + node-group pairs until the line is exhausted\n while (remaining.length > 0) {\n let hasArrowStart: boolean\n let style: EdgeStyle\n let hasArrowEnd: boolean\n let edgeLabel: string | undefined\n let startMarkerKind: 'circle' | 'cross' | undefined\n let endMarkerKind: 'circle' | 'cross' | undefined\n\n /*\n * Optional edge id prefix: `A e1@--> B` (Mermaid v11.10.0+). Consumed\n * before the arrow so ARROW_REGEX still sees the link token at position\n * zero. `@` is not otherwise valid ahead of a link, so this cannot\n * shadow existing syntax.\n */\n let edgeId: string | undefined\n const edgeIdMatch = remaining.match(/^([\\w-]+)@(?=[-=<~ox])/)\n if (edgeIdMatch) {\n edgeId = edgeIdMatch[1]\n remaining = remaining.slice(edgeIdMatch[0].length)\n }\n\n const arrowMatch = remaining.match(ARROW_REGEX)\n const arrowBody = arrowMatch?.[2]\n const startMarker = arrowMatch?.[1]\n const endMarker = arrowMatch?.[3]\n\n if (\n arrowMatch &&\n arrowBody !== undefined &&\n // An unmarked `--`/`==` is the text-label opener, not a link.\n !(\n startMarker === undefined &&\n endMarker === undefined &&\n AMBIGUOUS_UNMARKED_BODIES.has(arrowBody)\n )\n ) {\n // `o`/`x` mark a circle/cross terminator (alongside `<` for a reversed\n // arrow) — `markerKind` records which, so the ASCII renderer can draw\n // a distinct glyph instead of the default arrowhead (see issue #330,\n // a follow-up to issue #65 which first stopped these from being\n // dropped entirely).\n hasArrowStart = startMarker !== undefined\n startMarkerKind = markerKind(startMarker)\n const rawEdgeLabel = arrowMatch[4]?.trim()\n edgeLabel = rawEdgeLabel ? normalizeBrTags(rawEdgeLabel) : undefined\n remaining = remaining.slice(arrowMatch[0].length).trim()\n style = arrowStyleFromBody(arrowBody)\n hasArrowEnd = endMarker !== undefined\n endMarkerKind = markerKind(endMarker)\n } else {\n // Fallback: text-embedded label syntax (-- Yes -->, -. Maybe .->, == Sure ==>)\n const textMatch = remaining.match(TEXT_ARROW_REGEX)\n if (!textMatch) break\n hasArrowStart = Boolean(textMatch[1])\n startMarkerKind = markerKind(textMatch[1])\n const rawLabel = textMatch[3]!.trim()\n edgeLabel = rawLabel ? normalizeBrTags(rawLabel) : undefined\n const openOp = textMatch[2]!\n const closeOp = textMatch[4]!\n remaining = remaining.slice(textMatch[0].length).trim()\n style = textArrowStyleFromOps(openOp, closeOp)\n // closeOp is a variable-length run (e.g. \"---o\", \"==x\", \"-.-.->\"); only\n // its final character can be a circle/cross marker. A circle/cross\n // terminator is its own kind of arrow ending — like `>` — so it counts\n // toward hasArrowEnd too; an unmarked run (`---`, `===`, `-.-`) is the\n // only case with no arrowhead of any kind at the close.\n endMarkerKind = markerKind(closeOp.slice(-1))\n hasArrowEnd = closeOp.endsWith('>') || endMarkerKind !== undefined\n }\n\n // Parse the next node group\n const nextGroup = consumeNodeGroup(remaining, graph, subgraphStack)\n if (!nextGroup || nextGroup.ids.length === 0) break\n\n remaining = nextGroup.remaining.trim()\n\n // Emit Cartesian product of edges: every source × every target\n for (const sourceId of prevGroupIds) {\n for (const targetId of nextGroup.ids) {\n graph.edges.push({\n source: sourceId,\n target: targetId,\n label: edgeLabel,\n style,\n hasArrowStart,\n hasArrowEnd,\n ...(startMarkerKind !== undefined\n ? { startMarker: startMarkerKind }\n : {}),\n ...(endMarkerKind !== undefined ? { endMarker: endMarkerKind } : {}),\n ...(edgeId !== undefined ? { id: edgeId } : {}),\n })\n }\n }\n\n prevGroupIds = nextGroup.ids\n }\n}\n\ninterface ConsumedNodeGroup {\n ids: string[]\n remaining: string\n}\n\n/**\n * Consume one or more nodes separated by `&`.\n * E.g. \"A & B & C --> ...\" returns ids: ['A', 'B', 'C']\n */\nfunction consumeNodeGroup(\n text: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): ConsumedNodeGroup | null {\n const first = consumeNode(text, graph, subgraphStack)\n if (!first) return null\n\n const ids = [first.id]\n let remaining = first.remaining.trim()\n\n // Check for & separators\n while (remaining.startsWith('&')) {\n remaining = remaining.slice(1).trim()\n const next = consumeNode(remaining, graph, subgraphStack)\n if (!next) break\n ids.push(next.id)\n remaining = next.remaining.trim()\n }\n\n return { ids, remaining }\n}\n\ninterface ConsumedNode {\n id: string\n remaining: string\n}\n\n/**\n * Try to consume a node definition from the start of `text`.\n * If the node has a shape+label (e.g. A[Text]), it's registered in the graph.\n * If it's a bare reference (e.g. A), we look it up or create a default.\n * Also handles ::: class shorthand suffix.\n */\nfunction consumeNode(\n text: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): ConsumedNode | null {\n let id: string | null = null\n let remaining: string = text\n\n // Check for ::: class shorthand appearing BEFORE the shape brackets\n // (e.g. A:::external[Label]). Strip it out so shape-pattern matching\n // below still sees the id directly adjacent to its brackets.\n let preClassName: string | undefined\n const preClassMatch = remaining.match(PRE_CLASS_SHORTHAND_REGEX)\n if (preClassMatch) {\n preClassName = preClassMatch[2]!\n remaining = preClassMatch[1]! + remaining.slice(preClassMatch[0].length)\n }\n\n /*\n * Expanded syntax: `A@{ shape: doc, label: \"Report\" }` (Mermaid v11.3.0+).\n *\n * Tried before the bracket patterns because the id is followed by `@{`,\n * which none of them match — without this the id would fall through to\n * BARE_NODE_REGEX and the whole metadata block would be stranded as\n * unparsed text.\n */\n const expandedIdMatch = remaining.match(EXPANDED_NODE_ID_REGEX)\n if (expandedIdMatch) {\n const expandedId = expandedIdMatch[1]!\n const block = matchExpandedBlock(remaining.slice(expandedId.length))\n if (block) {\n const meta = parseExpandedMeta(block.body)\n registerNode(graph, subgraphStack, {\n id: expandedId,\n label: normalizeBrTags(expandedNodeLabel(expandedId, meta)),\n shape: expandedNodeShape(meta),\n })\n id = expandedId\n remaining = remaining.slice(expandedId.length + block.length)\n }\n }\n\n // Try each node pattern (shape-qualified)\n if (id === null) {\n for (const { regex, shape } of NODE_PATTERNS) {\n const match = remaining.match(regex)\n if (match) {\n id = match[1]!\n const label = normalizeBrTags(match[2]!)\n // The slash-bracket family resolves its shape from the closing\n // delimiter it captured in group 3; every other pattern has a fixed\n // shape. See the SLASH_BRACKET note in NODE_PATTERNS.\n const resolvedShape =\n typeof shape === 'function' ? shape(match[3] ?? '') : shape\n registerNode(graph, subgraphStack, { id, label, shape: resolvedShape })\n remaining = remaining.slice(match[0].length)\n break\n }\n }\n }\n\n // Bare node reference — only register if node doesn't exist yet.\n // If it already exists, do NOT track it in the current subgraph;\n // nodes belong to the subgraph where they're first defined.\n if (id === null) {\n const bareMatch = remaining.match(BARE_NODE_REGEX)\n if (bareMatch) {\n id = bareMatch[1]!\n if (!graph.nodes.has(id)) {\n registerNode(graph, subgraphStack, {\n id,\n label: id,\n shape: 'rectangle',\n })\n }\n remaining = remaining.slice(bareMatch[0].length)\n }\n }\n\n if (id === null) return null\n\n if (preClassName) {\n graph.classAssignments.set(id, preClassName)\n }\n\n // Check for ::: class shorthand suffix immediately after the node\n const classMatch = remaining.match(CLASS_SHORTHAND_REGEX)\n if (classMatch) {\n graph.classAssignments.set(id, classMatch[1]!)\n remaining = remaining.slice(classMatch[0].length)\n }\n\n return { id, remaining }\n}\n\n/** Register a node in the graph and track it in the current subgraph */\nfunction registerNode(\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n node: MermaidNode,\n): void {\n const isNew = !graph.nodes.has(node.id)\n if (isNew) {\n graph.nodes.set(node.id, node)\n }\n trackInSubgraph(subgraphStack, node.id)\n}\n\n/** Add node ID to the innermost subgraph if we're inside one */\nfunction trackInSubgraph(\n subgraphStack: MermaidSubgraph[],\n nodeId: string,\n): void {\n if (subgraphStack.length > 0) {\n const current = subgraphStack[subgraphStack.length - 1]!\n if (!current.nodeIds.includes(nodeId)) {\n current.nodeIds.push(nodeId)\n }\n }\n}\n\n/**\n * Map a link body to its edge style, ignoring direction and run length.\n *\n * The body is the run between the optional start/end markers: `--`, `----`,\n * `==`, `-.-`, `-..-`, `~~~`, etc. Classification is by the characters used,\n * not the length, so every run length of a given style behaves identically.\n */\nfunction arrowStyleFromBody(body: string): EdgeStyle {\n if (body.startsWith('~')) return 'invisible'\n if (body.includes('.')) return 'dotted'\n if (body.startsWith('=')) return 'thick'\n return 'solid'\n}\n\n/** Map text-embedded arrow open/close operators to edge style */\nfunction textArrowStyleFromOps(openOp: string, closeOp: string): EdgeStyle {\n // Classify by the characters used, not by exact token, so every run length\n // of a given style resolves identically — the same rule arrowStyleFromBody\n // applies to the plain arrow forms.\n if (openOp.includes('.') || closeOp.includes('.')) return 'dotted'\n if (openOp.startsWith('=') || closeOp.startsWith('=')) return 'thick'\n return 'solid'\n}\n","// ============================================================================\n// zombie-mermaid — SVG diagram-type registry\n//\n// A per-type registration table `renderMermaidSVG` (./index.ts) looks up\n// instead of hand-maintaining its own `switch (diagramType)` over the same\n// `DiagramType`. Moved here from the umbrella's `src/diagram-registry.ts`\n// under issue #1111, for parity with `@zombie-mermaid/ascii-renderer`'s own\n// `registry.ts` — see docs/decisions/diagram-type-registry-partial.md for\n// why the SVG and ASCII halves stayed two separate tables rather than one\n// shared `DiagramModule` with both a `renderSvg` and a `renderAscii` method\n// (short version: one table made this module import out of\n// `packages/ascii-renderer/`, while that package's `index.ts` imported this\n// one back, a cycle that blocked the monorepo split and dragged `elkjs`\n// into `dist/ascii.js`).\n//\n// Every diagram type is registered here, including 'flowchart' — see the\n// decision doc for why it was the last holdout (its ASCII path had no\n// per-type wrapper function to slot in until #745 extracted\n// `renderFlowchartAscii`, and its SVG path carries `%%{init: ...}%%`\n// directive handling no other type has — see `flowchartModule.layoutForSvg`\n// below for how that's folded in without growing `SvgRenderContext`). The\n// front door (`renderMermaidSVG` in ./index.ts) has no fallback switch left;\n// every `DiagramType` is a hit here.\n//\n// `packages/core/src/diagram-type.ts` (the `DiagramType` union + `detectDiagramType`)\n// stays exactly as-is and is what the front door uses to key into this\n// table — this module doesn't touch detection.\n//\n// `parseMermaid` (flowchart/state parsing) reaches out to the umbrella's\n// `../../../src/parser.ts` rather than a `@zombie-mermaid/*` package\n// specifier — flowchart/state parsing was deliberately never extracted into\n// `@zombie-mermaid/mermaid-parser` (see that package's scoping doc), the same\n// pre-existing boundary call `packages/ascii-renderer/src/flowchart.ts`\n// already reaches across for the ASCII side. `vite.config.ts` bundles that\n// file straight into this package's own dist, exactly as it already does\n// for `ascii-renderer` — see that file's header comment.\n// ============================================================================\n\nimport type {\n DiagramType,\n RenderOptions,\n DiagramColors,\n SvgEmitOptions,\n MermaidGraph,\n PositionedGraph,\n CurveStyle,\n Statement,\n} from '@zombie-mermaid/core'\nimport type { FontSizes } from './styles.ts'\nimport { withDirectionOverride } from '@zombie-mermaid/core'\n\nimport {\n parseXYChart,\n parseErDiagram,\n parseSequenceDiagram,\n parseClassDiagram,\n parseArchitecture,\n architectureToGraph,\n parseC4Diagram,\n} from '@zombie-mermaid/mermaid-parser'\nimport type {\n XYChart,\n PositionedXYChart,\n ErDiagram,\n PositionedErDiagram,\n SequenceDiagram,\n PositionedSequenceDiagram,\n ClassDiagram,\n PositionedClassDiagram,\n ArchitectureDiagram,\n C4Diagram,\n PositionedC4Diagram,\n} from '@zombie-mermaid/mermaid-parser'\nimport { layoutXYChart } from './xychart/layout.ts'\nimport { renderXYChartSvg } from './xychart/renderer.ts'\nimport { layoutErDiagramSync } from './er/layout.ts'\nimport { renderErSvg } from './er/renderer.ts'\nimport { layoutSequenceDiagram } from './sequence/layout.ts'\nimport { renderSequenceSvg } from './sequence/renderer.ts'\nimport { layoutClassDiagramSync } from './class/layout.ts'\nimport { renderClassSvg } from './class/renderer.ts'\nimport { layoutC4DiagramSync } from './c4/layout.ts'\nimport { renderC4Svg } from './c4/renderer.ts'\nimport { layoutGraphSync } from './layout-engine.ts'\nimport { renderSvg as renderFlowchartSvg } from './renderer.ts'\nimport { parseMermaid } from '../../../src/parser.ts'\n\n/**\n * Parameters shared by every per-type SVG renderer today, factored out of\n * the positional-argument lists that otherwise differ type to type (the\n * inconsistency issue #533 flags — confirmed by reading all five renderer\n * signatures: `embedSource`/`title`/`decorative`/`emit` shift position\n * across `renderSvg`, `renderSequenceSvg`, `renderClassSvg`, `renderErSvg`,\n * `renderXYChartSvg`, and `renderErSvg` drops `linksEnabled` entirely).\n *\n * Fields only one or two types need (`curve`, `animationEnabled`,\n * `linksEnabled`, `interactive`) are deliberately left OUT of this shared\n * shape rather than grown in for every exception — each type's own\n * `renderSvg` adapter below derives those straight from `options`, exactly\n * as each existing `renderMermaidSVGRaw` switch case already did.\n */\nexport interface SvgRenderContext {\n colors: DiagramColors\n font: string\n transparent: boolean\n fontSizes: FontSizes\n embedSource?: string\n title?: string\n decorative?: boolean\n emit: SvgEmitOptions\n}\n\n/**\n * One diagram type's full registration.\n *\n * `layoutForSvg` is deliberately SVG-only — it is NOT shared with the ASCII\n * side, unlike the issue's original `{ detect, parse, layout, renderSvg,\n * renderAscii }` sketch. Reading every ASCII per-type module confirmed why a\n * single shared `layout` step would be fiction, not simplification, for this\n * codebase: SVG layout produces pixel coordinates (`PositionedXYChart`,\n * `PositionedErDiagram`, …), while every ASCII renderer does its own,\n * unrelated grid/canvas layout internally — see e.g.\n * `packages/ascii-renderer/src/xychart.ts`'s file header: \"Uses the parsed\n * XYChart type directly (not PositionedXYChart) since pixel coordinates\n * don't map to character grids.\" `parse` genuinely is shared in the sense\n * that both front doors call the same `parseXYChart`/`parseErDiagram`, but\n * each ASCII renderer reruns that parse itself from raw text — which is why\n * the ASCII entries live in `packages/ascii-renderer/src/registry.ts` as\n * plain `(text, …) => string` functions instead of a `renderAscii` method on\n * this interface.\n *\n * `parse` takes both `lines` (the pre-split statement list from\n * `splitStatements(decoded)`, computed once by the front door — what every\n * already-registered type's parser wants) and `text` (the raw, un-split\n * source `flowchartModule` below needs instead): `parseMermaid`'s\n * `%%{init: ...}%%` directive extraction reads raw, un-commented lines that\n * `splitStatements` has already discarded by the time `lines` exists, and\n * its multi-line-statement continuation merging needs each statement's\n * *originating physical line* grouping, which `splitStatements`'s flattened\n * array has already lost. `xychartModule`/`erModule`/`sequenceModule`/\n * `classModule` all ignore the second parameter — JS/TS functions may take\n * fewer parameters than their declared type allows, so `parse: parseXYChart`\n * (etc.) is unchanged from before this parameter was added.\n */\nexport interface DiagramModule<TDiagram = unknown, TPositioned = unknown> {\n readonly type: DiagramType\n parse(lines: Statement[], text: string): TDiagram\n layoutForSvg(diagram: TDiagram, options: RenderOptions): TPositioned\n renderSvg(\n positioned: TPositioned,\n ctx: SvgRenderContext,\n options: RenderOptions,\n ): string\n}\n\nconst xychartModule: DiagramModule<XYChart, PositionedXYChart> = {\n type: 'xychart',\n parse: parseXYChart,\n layoutForSvg: layoutXYChart,\n renderSvg(positioned, ctx, options) {\n // Mirrors resolveXYChartInteractive() in the umbrella's src/index.ts\n // exactly — `interactivity` wins when set, otherwise the deprecated\n // `interactive` boolean keeps controlling this as before.\n const interactive =\n options.interactivity !== undefined\n ? options.interactivity === 'full'\n : (options.interactive ?? false)\n return renderXYChartSvg(\n positioned,\n ctx.colors,\n ctx.font,\n ctx.transparent,\n interactive,\n ctx.embedSource,\n ctx.title,\n ctx.decorative,\n ctx.emit,\n )\n },\n}\n\nconst sequenceModule: DiagramModule<\n SequenceDiagram,\n PositionedSequenceDiagram\n> = {\n type: 'sequence',\n parse: parseSequenceDiagram,\n layoutForSvg: layoutSequenceDiagram,\n renderSvg(positioned, ctx) {\n return renderSequenceSvg(\n positioned,\n ctx.colors,\n ctx.font,\n ctx.transparent,\n ctx.fontSizes,\n ctx.embedSource,\n ctx.title,\n ctx.decorative,\n ctx.emit,\n )\n },\n}\n\n/**\n * Mirrors `resolveLinksEnabled()` in the umbrella's src/index.ts exactly\n * (`interactivity` defaults unset to `'static'`; only `'none'` turns links\n * off) — duplicated here rather than imported since that helper is private\n * to src/index.ts, which now imports this module (importing it back would\n * cycle).\n */\nfunction resolveLinksEnabled(options: RenderOptions): boolean {\n const interactivity = options.interactivity ?? 'static'\n return interactivity !== 'none'\n}\n\n/**\n * Mirrors `resolveAnimationEnabled()` in the umbrella's src/index.ts exactly\n * (`interactivity` defaults unset to `'static'`; only `'full'` turns\n * animation on) — duplicated here for the same import-direction reason\n * `resolveLinksEnabled` above is.\n */\nfunction resolveAnimationEnabled(options: RenderOptions): boolean {\n const interactivity = options.interactivity ?? 'static'\n return interactivity === 'full'\n}\n\nconst classModule: DiagramModule<ClassDiagram, PositionedClassDiagram> = {\n type: 'class',\n parse: parseClassDiagram,\n layoutForSvg: layoutClassDiagramSync,\n renderSvg(positioned, ctx, options) {\n return renderClassSvg(\n positioned,\n ctx.colors,\n ctx.font,\n ctx.transparent,\n ctx.fontSizes,\n ctx.embedSource,\n ctx.title,\n ctx.decorative,\n resolveLinksEnabled(options),\n ctx.emit,\n )\n },\n}\n\nconst erModule: DiagramModule<ErDiagram, PositionedErDiagram> = {\n type: 'er',\n parse: parseErDiagram,\n // `options.direction` replaces the diagram's own top-level `direction`\n // line, if any, before layout — same order as the umbrella's original\n // 'er' case: parse, then withDirectionOverride, then layout.\n layoutForSvg(diagram, options) {\n return layoutErDiagramSync(\n withDirectionOverride(diagram, options.direction),\n options,\n )\n },\n renderSvg(positioned, ctx) {\n return renderErSvg(\n positioned,\n ctx.colors,\n ctx.font,\n ctx.transparent,\n ctx.fontSizes,\n ctx.embedSource,\n ctx.title,\n ctx.decorative,\n ctx.emit,\n )\n },\n}\n\n/**\n * `renderSvg`'s (the low-level flowchart/state SVG emitter, imported above\n * as `renderFlowchartSvg`) `curve` parameter is the one piece of\n * `SvgRenderContext`-adjacent state no other registered type needs: a\n * `%%{init: {\"flowchart\": {\"curve\": ...}}}%%` directive on the diagram\n * itself can supply a default, and `options.curve` always wins when set —\n * see `applyInitConfig()` in `packages/core/src/init-directive.ts`, which\n * the umbrella's pre-registry flowchart path used to call directly.\n *\n * That resolution needs the parsed diagram (for `initConfig`) AND the\n * caller's `options` together, and the *result* is only needed later, by\n * `renderSvg` — so rather than teach `SvgRenderContext` a diagram-specific\n * `curve` field (every other field there is genuinely shared across types),\n * `layoutForSvg` below resolves it once and carries it forward on\n * `PositionedFlowchart`, right next to the `PositionedGraph` it was\n * resolved alongside. `animationEnabled`/`linksEnabled` need no such\n * carry-through: both derive from `options` alone via the\n * `resolveAnimationEnabled`/`resolveLinksEnabled` helpers above, exactly\n * like `class`'s `linksEnabled` already does, so `flowchartModule.renderSvg`\n * just calls them directly.\n */\nexport interface PositionedFlowchart {\n graph: PositionedGraph\n curve: CurveStyle\n}\n\nconst flowchartModule: DiagramModule<MermaidGraph, PositionedFlowchart> = {\n type: 'flowchart',\n // Ignores `lines` — see the `parse` doc comment on `DiagramModule` above\n // for why flowchart/state needs the raw `text` instead.\n parse: (_lines, text) => parseMermaid(text),\n layoutForSvg(diagram, options) {\n // Same order as the umbrella's original fallback: direction override,\n // then layout. `options.curve` wins over the diagram's own\n // `%%{init: ...}%%` directive, which wins over the 'linear' default —\n // see the doc comment on `PositionedFlowchart` above.\n const graph = withDirectionOverride(diagram, options.direction)\n return {\n graph: layoutGraphSync(graph, options),\n curve: options.curve ?? diagram.initConfig?.curve ?? 'linear',\n }\n },\n renderSvg(positioned, ctx, options) {\n return renderFlowchartSvg(\n positioned.graph,\n ctx.colors,\n ctx.font,\n ctx.transparent,\n ctx.fontSizes,\n positioned.curve,\n ctx.embedSource,\n resolveAnimationEnabled(options),\n resolveLinksEnabled(options),\n ctx.title,\n ctx.decorative,\n ctx.emit,\n )\n },\n}\n\nconst c4Module: DiagramModule<C4Diagram, PositionedC4Diagram> = {\n type: 'c4',\n parse: parseC4Diagram,\n // Mermaid's C4 renderer has no direction: shapes are placed in rows in\n // declaration order, so `options.direction` does not apply to the SVG.\n layoutForSvg(diagram, options) {\n return layoutC4DiagramSync(diagram, options)\n },\n renderSvg(positioned, ctx) {\n return renderC4Svg(\n positioned,\n ctx.colors,\n ctx.font,\n ctx.transparent,\n ctx.fontSizes,\n ctx.embedSource,\n ctx.title,\n ctx.decorative,\n ctx.emit,\n )\n },\n}\n\n/**\n * Architecture diagrams are lowered to the flowchart model\n * (`architectureToGraph` in `@zombie-mermaid/mermaid-parser`) and ride the\n * flowchart layout + renderer, so this module is `flowchartModule` with a\n * different `parse`. The lowered graph carries no `%%{init}%%` config, so\n * `curve` is `options.curve` or 'linear'.\n */\nconst architectureModule: DiagramModule<\n ArchitectureDiagram,\n PositionedFlowchart\n> = {\n type: 'architecture',\n parse: parseArchitecture,\n layoutForSvg(diagram, options) {\n const graph = withDirectionOverride(\n architectureToGraph(diagram),\n options.direction,\n )\n return {\n graph: layoutGraphSync(graph, options),\n curve: options.curve ?? 'linear',\n }\n },\n renderSvg: flowchartModule.renderSvg,\n}\n\n/**\n * The registry proper — every `DiagramType` is looked up here by the SVG\n * front door (`renderMermaidSVG` in ./index.ts), which has no fallback\n * switch left. The ASCII front door's equivalent table is `asciiRegistry`\n * in `packages/ascii-renderer/src/registry.ts`.\n *\n * Typed with `any` type parameters at the map level: each entry's own\n * `TDiagram`/`TPositioned` are only known inside that entry's own closure\n * (see `xychartModule`/`erModule`/`flowchartModule` above, which are fully\n * typed); the map just needs one consistent shape to hold heterogeneous\n * entries in.\n */\ntype AnyDiagramModule = DiagramModule<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see comment above; each entry is fully typed at its own definition site.\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see comment above; each entry is fully typed at its own definition site.\n any\n>\n\nexport const diagramRegistry: Record<DiagramType, AnyDiagramModule> = {\n xychart: xychartModule,\n er: erModule,\n sequence: sequenceModule,\n class: classModule,\n flowchart: flowchartModule,\n architecture: architectureModule,\n\n c4: c4Module,\n}\n","// ============================================================================\n// @zombie-mermaid/svg-renderer — SVG emission and ELK-backed layout\n//\n// Every file re-exported below was grep-verified (zombie-mermaid#625,\n// umbrella #620) as unreachable from `src/ascii/index.ts` except through\n// the SVG-side modules the umbrella still owns — so nothing here can put\n// `elkjs` on the ASCII renderer's dependency path once #623 extracts it.\n//\n// This package depends on `@zombie-mermaid/core` and, since #624, on\n// `@zombie-mermaid/mermaid-parser` too — an ordinary, acyclic dependency\n// (both `svg-renderer` and the future `ascii-renderer` depend on\n// `mermaid-parser` directly, per the scoping doc's finding 2), not a cycle:\n// every per-type `layout.ts`/`renderer.ts` below needs the positioned-\n// diagram types `mermaid-parser`'s `types.ts` halves own, and\n// `class/layout.ts` needs `formatClassMember` from `mermaid-parser`'s\n// `class/format.ts`. It deliberately does NOT re-export the internal\n// `layout-engine/` modules the umbrella never imports\n// (`constants.ts` — whose `DEFAULTS` would collide with `core`'s theme\n// `DEFAULTS` anyway — plus `edge-bundling.ts`, `from-elk.ts`,\n// `layer-alignment.ts` and `to-elk.ts`): they are `layoutGraphSync()`'s\n// implementation, not its API. `elk-graph-builder.ts` is the exception:\n// `class/layout.ts` and `er/layout.ts` (moved into this package under\n// #624) call its primitives directly (zombie-mermaid#616), so it is part\n// of the public API alongside `elk-adapter-utils.ts`.\n//\n// `layout.ts` used to be a thin re-export of `layoutGraphSync` from\n// `layout-engine.ts`; it was folded into `layout-engine.ts` directly\n// (#1109), so `layoutGraphSync` now comes from the one star-export below.\n//\n// `class/`, `er/`, `sequence/`, `xychart/` hold each diagram type's\n// renderer half (`layout.ts` + `renderer.ts`) — the other half\n// (`parser.ts`/`types.ts`, plus `class/format.ts`, `sequence/box-color.ts`,\n// `sequence/activation-check.ts`, `xychart/colors.ts`) moved to\n// `@zombie-mermaid/mermaid-parser` under the same issue (#624), per the\n// scoping doc's finding 1: each per-type directory used to mix both halves\n// in one place, and the split point already existed at the file level.\n//\n// `renderMermaidSVG` (below) is the single \"Mermaid text in, SVG out\" front\n// door — moved here from the umbrella's `src/index.ts` under issue #1111,\n// for parity with `@zombie-mermaid/ascii-renderer`'s `renderMermaidASCII`.\n// `./registry.ts` (not re-exported — same as ascii-renderer's own\n// `registry.ts` — it's this front door's internal dispatch table, not part\n// of the public API) drives per-diagram-type dispatch; see that file's\n// header for why it's a separate module from the ASCII side's table.\n// ============================================================================\n\nexport * from './edge-curves.ts'\nexport * from './elk-instance.ts'\nexport * from './layout-engine.ts'\nexport * from './layout-engine/elk-adapter-utils.ts'\nexport * from './layout-engine/elk-graph-builder.ts'\nexport * from './renderer.ts'\nexport * from './resolve-colors.ts'\nexport * from './shape-clipping.ts'\nexport * from './styles.ts'\n\nexport * from './class/layout.ts'\nexport * from './class/renderer.ts'\nexport * from './er/layout.ts'\nexport * from './er/renderer.ts'\nexport * from './sequence/layout.ts'\nexport * from './sequence/renderer.ts'\nexport * from './xychart/layout.ts'\nexport * from './xychart/renderer.ts'\n\nimport { decodeXML } from 'entities'\nimport type {\n RenderOptions,\n DiagramColors,\n SvgEmitOptions,\n DiagramType,\n} from '@zombie-mermaid/core'\nimport {\n DEFAULTS,\n themeStyleDeclarations,\n isMonospaceFont,\n setMonospaceMetrics,\n detectDiagramType,\n splitStatements,\n} from '@zombie-mermaid/core'\nimport { resolveCssColors } from './resolve-colors.ts'\nimport { resolveFontSizes } from './styles.ts'\nimport { diagramRegistry } from './registry.ts'\nimport type { SvgRenderContext } from './registry.ts'\n\n/**\n * Build a DiagramColors object from render options.\n * Uses DEFAULTS for bg/fg when not provided, and passes through\n * optional enrichment colors (line, accent, muted, surface, border).\n */\nfunction buildColors(options: RenderOptions): DiagramColors {\n return {\n bg: options.bg ?? DEFAULTS.bg,\n fg: options.fg ?? DEFAULTS.fg,\n line: options.line,\n accent: options.accent,\n muted: options.muted,\n surface: options.surface,\n border: options.border,\n }\n}\n\n/**\n * The exact CSS declaration list the root `<svg style=\"…\">` attribute would\n * carry for these options — `--bg`, `--fg`, whichever enrichment colours\n * were given, and (unless `transparent`) `background: var(--bg)`.\n *\n * For hosts with a strict `Content-Security-Policy`: a `style=` attribute\n * can't be nonced, so a `style-src` without `'unsafe-inline'` drops it and\n * the diagram loses its colours. Render with `styleAttribute: false` and\n * put this string in your own stylesheet on the SVG (or any ancestor —\n * custom properties inherit) instead. Pass the same options object to both\n * calls so the declarations match what the render expects. See\n * `RenderOptions.styleAttribute` / `RenderOptions.nonce` and issue #216.\n *\n * Built by the same function that fills the attribute in normal renders,\n * so there is one variable list to keep in sync. The string is compact\n * (`--bg:#fff;--fg:#000;background:var(--bg)`) — valid inside any rule\n * block — and the colour values are yours, unescaped, exactly as the\n * attribute has always carried them.\n *\n * @example\n * ```ts\n * const opts = { bg: '#1a1b26', fg: '#a9b1d6', nonce, styleAttribute: false }\n * const svg = renderMermaidSVG('graph TD\\n A --> B', opts)\n * const css = `.diagram svg { ${themeCssVariables(opts)} }`\n * ```\n */\nexport function themeCssVariables(options: RenderOptions = {}): string {\n return themeStyleDeclarations(buildColors(options), options.transparent)\n}\n\n/**\n * Resolve the effective strict-CSP emission controls from the public\n * options. Kept as one object so every renderer takes it as a single\n * trailing parameter — see `SvgEmitOptions` in packages/core/src/theme.ts.\n */\nfunction resolveSvgEmit(options: RenderOptions): SvgEmitOptions {\n return {\n nonce: options.nonce,\n styleAttribute: options.styleAttribute,\n }\n}\n\n// Interactivity-derived render gates — whether xychart hover tooltips\n// render, whether flowchart/state edge animation (`e1@{ animate: true }`)\n// plays, and whether `click`-based links/`<title>` tooltips render — all\n// now live next to their diagram type's registry entry in ./registry.ts\n// (`resolveAnimationEnabled`/`resolveLinksEnabled` there, duplicated rather\n// than imported since that module is imported BY this file — see those\n// functions' own comments for why), since every diagram type's SVG dispatch\n// is fully handled by the registry lookup below and there is no remaining\n// switch case here for them to serve.\n\n/**\n * Render Mermaid diagram text to an SVG string — synchronously.\n *\n * Uses elk.bundled.js with a direct FakeWorker bypass (no setTimeout(0) delay).\n * The ELK singleton is created lazily on first use and cached forever.\n *\n * Use this in React components with useMemo() to avoid flash:\n * const svg = useMemo(() => renderMermaidSVG(code, opts), [code])\n *\n * @param text - Mermaid source text\n * @param options - Rendering options (colors, font, spacing)\n * @returns A self-contained SVG string\n *\n * @example\n * ```ts\n * const svg = renderMermaidSVG('graph TD\\n A --> B')\n *\n * // With theme\n * const svg = renderMermaidSVG('graph TD\\n A --> B', {\n * bg: '#1a1b26', fg: '#a9b1d6'\n * })\n *\n * // With CSS variables (for live theme switching)\n * const svg = renderMermaidSVG('graph TD\\n A --> B', {\n * bg: 'var(--background)', fg: 'var(--foreground)', transparent: true\n * })\n *\n * // With the original source stamped onto the root <svg> as data-src —\n * // handy for a \"copy source\" button or an \"open in Mermaid Live\" link\n * // without re-attaching it via string surgery on the output.\n * const svg = renderMermaidSVG('graph TD\\n A --> B', { embedSource: true })\n *\n * // With an accessible name — role=\"img\" + aria-labelledby pointing at a\n * // <title> child, so assistive tech announces the diagram instead of\n * // reading every node label individually (see issue #215).\n * const svg = renderMermaidSVG('graph TD\\n A --> B', {\n * title: 'Flowchart: Build → Test → Ship'\n * })\n *\n * // Decorative diagram — already described in surrounding prose, so it's\n * // hidden from assistive tech (aria-hidden=\"true\") instead of named.\n * const svg = renderMermaidSVG('graph TD\\n A --> B', { decorative: true })\n * ```\n */\nexport function renderMermaidSVG(\n text: string,\n options: RenderOptions = {},\n): string {\n const svg = renderMermaidSVGRaw(text, options)\n return options.resolveColors\n ? resolveCssColors(svg, buildColors(options))\n : svg\n}\n\n/** The renderer proper — `renderMermaidSVG` minus the optional `resolveColors` post-pass. */\nfunction renderMermaidSVGRaw(text: string, options: RenderOptions): string {\n // Decode XML entities that may leak from markdown parsers (e.g. rehype-raw).\n // Without this, escapeXml() double-encodes them: &lt; → &amp;lt; → literal \"&lt;\" in SVG.\n // `text` itself is left untouched so `embedSource` below stamps the exact\n // string the caller passed in, not this entity-decoded copy used\n // internally for parsing.\n const decoded = decodeXML(text)\n\n const colors = buildColors(options)\n const font = options.font ?? 'Inter'\n // Box sizing depends on the metrics model, so pick it before any layout runs.\n setMonospaceMetrics(isMonospaceFont(font))\n const transparent = options.transparent ?? false\n const fontSizes = resolveFontSizes(options.fontSizes)\n const diagramType: DiagramType = detectDiagramType(decoded)\n const embedSource = options.embedSource ? text : undefined\n const title = options.title\n const decorative = options.decorative\n const emit = resolveSvgEmit(options)\n\n const lines = splitStatements(decoded)\n\n // Registry dispatch (see ./registry.ts): every diagram type, including\n // 'flowchart' (and the 'state' pipeline it shares), is registered there.\n // `parse` takes both `lines` (what every already-registered type's parser\n // wants) and `decoded` (the raw text flowchart/state's parser needs\n // instead — see the `parse` doc comment on `DiagramModule` in\n // ./registry.ts).\n const registered = diagramRegistry[diagramType]\n const diagram = registered.parse(lines, decoded)\n const positioned = registered.layoutForSvg(diagram, options)\n const ctx: SvgRenderContext = {\n colors,\n font,\n transparent,\n fontSizes,\n embedSource,\n title,\n decorative,\n emit,\n }\n return registered.renderSvg(positioned, ctx, options)\n}\n\n/**\n * Render Mermaid diagram text to an SVG string — async.\n *\n * Same result as renderMermaidSVG() but returns a Promise.\n * Useful in async contexts (server handlers, data loaders, etc.)\n */\nexport async function renderMermaidSVGAsync(\n text: string,\n options: RenderOptions = {},\n): Promise<string> {\n return renderMermaidSVG(text, options)\n}\n\n// ---------------------------------------------------------------------------\n// Backward-compatible aliases\n// ---------------------------------------------------------------------------\n\n/** @deprecated Use `renderMermaidSVG` */\nexport const renderMermaidSync = renderMermaidSVG\n\n/** @deprecated Use `renderMermaidSVGAsync` */\nexport const renderMermaid = renderMermaidSVGAsync\n"],"mappings":"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"}