@zombie-mermaid/svg-renderer 2.2.1

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.
@@ -0,0 +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"],"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 } 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 const segments = new Map<number, EdgeSegmentGroup>()\n collectEdgeSegments(elkNode, segments, 0, 0)\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 * 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): 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 // Extract points and label position\n const points = extractEdgePoints(elkEdge, offsetX, offsetY)\n const labelPosition = extractEdgeLabelPosition(elkEdge, offsetX, offsetY)\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 )\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"],"mappings":"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"}