@zombie-mermaid/core 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +8 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +98 -76
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/diagram-type.test.ts +38 -0
- package/src/__tests__/render-utils.test.ts +59 -0
- package/src/diagram-type.ts +24 -1
- package/src/index.ts +1 -0
- package/src/multiline-utils.ts +6 -5
- package/src/render-utils.ts +54 -0
- package/src/style-directives.ts +6 -1
- package/src/theme.ts +3 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/click-directive.ts","../src/color-utils.ts","../src/statements.ts","../src/diagram-type.ts","../src/direction.ts","../src/direction-override.ts","../src/init-directive.ts","../src/text-metrics.ts","../src/multiline-utils.ts","../src/style-directives.ts","../src/generated/mono-font-subset.ts","../src/theme.ts"],"sourcesContent":["import type { NodeInteraction } from './types.ts'\n\n// ============================================================================\n// Shared `click` directive parsing and href sanitization.\n//\n// Every diagram type that recognizes Mermaid's `click` statement (flowchart/\n// state via src/parser.ts, class diagrams via\n// packages/mermaid-parser/src/class/parser.ts) shares this exact grammar\n// and the same href-safety rules — pulled out here so\n// neither copy can drift from the other. See\n// docs/decisions/no-script-interactivity.md for the tier model this\n// implements: an `href` becomes a real `<a>` link (tier 2), a tooltip\n// becomes a `<title>` (tier 1), and a `call`/`callback` binding is recorded\n// as data but never executed (tier 3 stays unimplemented, permanently).\n// ============================================================================\n\n/**\n * Parse a `click <id> ...` statement and record the resulting\n * {@link NodeInteraction} in `interactions`, keyed by whatever id scheme the\n * caller's diagram type uses (flowchart/state node id, class diagram class\n * id, ...).\n *\n * Supported forms:\n * click id \"url\" [\"tooltip\"] [_blank|_self|_parent|_top]\n * click id href \"url\" [\"tooltip\"] [_blank|_self|_parent|_top]\n * click id call fn() [\"tooltip\"]\n * click id callback fn() [\"tooltip\"]\n *\n * A line that doesn't match `click <id> ...` at all is a no-op — callers are\n * expected to have already gated on a leading `click` keyword before calling\n * this (see the `/^click\\s+/i` checks at each call site) purely so this\n * function's own regex can stay anchored to the id capture.\n */\nexport function applyClickStatement(\n line: string,\n interactions: Map<string, NodeInteraction>,\n): void {\n const match = line.match(/^click\\s+([\\w\\p{L}-]+)\\s+(.*)$/iu)\n if (!match) return\n\n const nodeId = match[1]!\n let rest = match[2]!.trim()\n\n const interaction: NodeInteraction = { ...interactions.get(nodeId) }\n\n // `call fn()` / `callback fn()` — a script binding.\n const callMatch = rest.match(/^(?:call|callback)\\s+(.+?)\\s*$/i)\n if (callMatch) {\n // A trailing quoted tooltip may follow the callback expression.\n const withTooltip = callMatch[1]!.match(/^(.*?\\))\\s+\"([^\"]*)\"\\s*$/)\n if (withTooltip) {\n interaction.callback = withTooltip[1]!.trim()\n interaction.tooltip = withTooltip[2]\n } else {\n interaction.callback = callMatch[1]!.trim()\n }\n interactions.set(nodeId, interaction)\n return\n }\n\n // Optional explicit `href` keyword.\n rest = rest.replace(/^href\\s+/i, '')\n\n // Remaining tokens: \"url\" [\"tooltip\"] [_target]\n const quoted = [...rest.matchAll(/\"([^\"]*)\"/g)].map((m) => m[1]!)\n if (quoted.length > 0) interaction.href = quoted[0]\n if (quoted.length > 1) interaction.tooltip = quoted[1]\n\n const targetMatch = rest.match(/(_blank|_self|_parent|_top)\\s*$/i)\n if (targetMatch) interaction.target = targetMatch[1]!.toLowerCase()\n\n if (interaction.href !== undefined || interaction.tooltip !== undefined) {\n interactions.set(nodeId, interaction)\n }\n}\n\n/**\n * Accept only link schemes that cannot execute script.\n *\n * A `click` target comes from diagram text, which may be untrusted. An\n * `href` of `javascript:...` (or a `data:` URL containing markup) would turn\n * a rendered diagram into an XSS vector for any page that inlines the SVG, so\n * anything but http/https/mailto and same-document or relative references is\n * dropped rather than emitted.\n *\n * C0 controls are rejected outright, before any other check. The URL parser\n * strips tab and newline from *anywhere* in a URL, so `java\\tscript:` reaches\n * a browser as `javascript:` — while the scheme match below sees `java\\t…`,\n * finds no scheme, and waves it through as a relative reference. Splitting a\n * blocked scheme with a control character is the whole bypass; there is no\n * legitimate URL with a raw control in it, so the entire range goes.\n */\nexport function safeHref(href: string | undefined): string | undefined {\n if (!href) return undefined\n\n if (/[\\x00-\\x1F\\x7F]/.test(href)) return undefined\n\n const trimmed = href.trim()\n // Relative, absolute-path, and fragment references carry no scheme.\n if (/^[./#?]/.test(trimmed)) return trimmed\n\n const scheme = trimmed.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/)?.[1]\n if (scheme === undefined) return trimmed\n\n return /^(https?|mailto)$/i.test(scheme) ? trimmed : undefined\n}\n","// ============================================================================\n// CSS color primitives — parse, mix, and format sRGB colors.\n//\n// Shared by the SVG theme system (packages/core/src/theme.ts), the ASCII renderer's\n// DiagramColors → AsciiTheme bridge (src/ascii/ansi.ts), and the\n// `resolveColors` post-processor (packages/svg-renderer/src/resolve-colors.ts). All three need\n// the same answer to \"what does `color-mix(in srgb, fg 50%, bg)` come out\n// to?\", so the arithmetic lives here exactly once — the MIX percentages in\n// packages/core/src/theme.ts stay the single source of truth for *which* percentages the\n// renderer uses, and this module is the single source of truth for what a\n// percentage *means*.\n// ============================================================================\n\n/** An sRGB color: 0-255 channels plus a 0-1 alpha. */\nexport interface RgbaColor {\n r: number\n g: number\n b: number\n a: number\n}\n\n/** Matches #rgb, #rgba, #rrggbb, #rrggbbaa hex color literals. */\nconst HEX_COLOR_RE =\n /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/\n\n/**\n * Matches `rgb()` / `rgba()` in both the legacy comma syntax\n * (`rgb(1, 2, 3)`, `rgba(1, 2, 3, 0.5)`) and the modern space syntax\n * (`rgb(1 2 3 / 50%)`). Channels may be integers or percentages; alpha may\n * be a 0-1 number or a percentage.\n */\nconst RGB_FUNC_RE =\n /^rgba?\\(\\s*([\\d.]+%?)\\s*[, ]\\s*([\\d.]+%?)\\s*[, ]\\s*([\\d.]+%?)\\s*(?:[,/]\\s*([\\d.]+%?)\\s*)?\\)$/i\n\n/**\n * Parse a hex color string into 0-255 RGB components, ignoring any alpha\n * digits. Returns null for anything that isn't a well-formed hex literal\n * (e.g. `var(--foo)`, named CSS colors, or attribute-injection payloads) —\n * the caller should treat those as unresolvable and fall back.\n */\nexport function parseHexColor(value: string): [number, number, number] | null {\n const rgba = parseHexRgba(value)\n return rgba ? [rgba.r, rgba.g, rgba.b] : null\n}\n\n/** Parse a hex color literal (3/4/6/8 digits) into an RgbaColor, or null. */\nexport function parseHexRgba(value: string): RgbaColor | null {\n const match = HEX_COLOR_RE.exec(value.trim())\n if (!match) return null\n // HEX_COLOR_RE has exactly one, non-optional capture group, so it's always\n // populated when `match` is non-null — but that's a fact about the regex's\n // construction, not something the type checker can see. Guard explicitly\n // (falling back to \"unresolvable\", same as a non-match) rather than\n // asserting past it, so a future edit to the pattern can't silently turn\n // this into a runtime `undefined`.\n const captured = match[1]\n if (captured === undefined) return null\n let hex = captured\n if (hex.length === 3 || hex.length === 4) {\n hex = hex\n .split('')\n .map((c) => c + c)\n .join('')\n }\n return {\n r: parseInt(hex.slice(0, 2), 16),\n g: parseInt(hex.slice(2, 4), 16),\n b: parseInt(hex.slice(4, 6), 16),\n a: hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1,\n }\n}\n\n/** Parse one rgb()/rgba() channel token: an integer/decimal or a percentage of 255. */\nfunction parseRgbChannel(token: string): number {\n return token.endsWith('%')\n ? (Number(token.slice(0, -1)) / 100) * 255\n : Number(token)\n}\n\n/** Parse an alpha token: a 0-1 number or a percentage. */\nfunction parseAlpha(token: string | undefined): number {\n if (token === undefined) return 1\n return token.endsWith('%') ? Number(token.slice(0, -1)) / 100 : Number(token)\n}\n\n/**\n * Parse a concrete CSS color value into sRGB components.\n *\n * Accepts the forms this library's own theme system produces or documents:\n * hex literals (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`), `rgb()`/`rgba()`,\n * and the `transparent` keyword. Anything else — CSS named colors, `hsl()`,\n * `var(...)`, an unresolved `color-mix(...)` — returns null so the caller\n * can leave it untouched rather than guess.\n */\nexport function parseCssColor(value: string): RgbaColor | null {\n const trimmed = value.trim()\n if (trimmed.toLowerCase() === 'transparent') {\n return { r: 0, g: 0, b: 0, a: 0 }\n }\n const hex = parseHexRgba(trimmed)\n if (hex) return hex\n const rgb = RGB_FUNC_RE.exec(trimmed)\n if (rgb) {\n const [, rTok, gTok, bTok, aTok] = rgb\n if (rTok === undefined || gTok === undefined || bTok === undefined) {\n return null\n }\n const color = {\n r: parseRgbChannel(rTok),\n g: parseRgbChannel(gTok),\n b: parseRgbChannel(bTok),\n a: parseAlpha(aTok),\n }\n if ([color.r, color.g, color.b, color.a].some((n) => Number.isNaN(n))) {\n return null\n }\n return color\n }\n return null\n}\n\n/**\n * Evaluate `color-mix(in srgb, c1 p1%, c2 p2%)` per CSS Color Module\n * Level 5 (https://www.w3.org/TR/css-color-5/#color-mix): percentages are\n * normalized so they sum to 100 (a sum under 100 additionally scales the\n * result's alpha by `sum / 100`), and the two colors are interpolated in\n * premultiplied-alpha space, so mixing an opaque color toward\n * `transparent` fades its alpha without darkening it.\n *\n * `p1`/`p2` follow the spec's omission rules: pass `undefined` for an\n * omitted percentage (both omitted → 50/50; one omitted → 100 minus the\n * other). Returns null when both percentages are zero, which the spec\n * defines as invalid.\n */\nexport function mixSrgb(\n c1: RgbaColor,\n c2: RgbaColor,\n p1?: number,\n p2?: number,\n): RgbaColor | null {\n let w1: number\n let w2: number\n if (p1 === undefined && p2 === undefined) {\n w1 = 50\n w2 = 50\n } else if (p1 === undefined) {\n w2 = p2 ?? 50\n w1 = 100 - w2\n } else if (p2 === undefined) {\n w1 = p1\n w2 = 100 - w1\n } else {\n w1 = p1\n w2 = p2\n }\n const sum = w1 + w2\n if (sum === 0) return null\n const alphaMultiplier = sum < 100 ? sum / 100 : 1\n w1 /= sum\n w2 /= sum\n\n const alpha = c1.a * w1 + c2.a * w2\n const premul = (ch1: number, ch2: number): number =>\n alpha === 0 ? 0 : (ch1 * c1.a * w1 + ch2 * c2.a * w2) / alpha\n\n return {\n r: premul(c1.r, c2.r),\n g: premul(c1.g, c2.g),\n b: premul(c1.b, c2.b),\n a: alpha * alphaMultiplier,\n }\n}\n\n/**\n * Mix `fg` into `bg` at `pct` percent — the shape every MIX entry in\n * packages/core/src/theme.ts uses (`color-mix(in srgb, var(--fg) N%, var(--bg))`).\n * Returns `fg` unchanged when either input isn't a concrete color this\n * module can parse (a `var(...)` reference, a named color).\n */\nexport function mixHexColors(fg: string, bg: string, pct: number): string {\n const f = parseCssColor(fg)\n const b = parseCssColor(bg)\n if (!f || !b) return fg\n const mixed = mixSrgb(f, b, pct, 100 - pct)\n return mixed ? formatCssColor(mixed) : fg\n}\n\nconst clampByte = (n: number): number =>\n Math.min(255, Math.max(0, Math.round(n)))\n\n/**\n * Serialize a color as `#rrggbb` when fully opaque, otherwise as\n * `rgba(r, g, b, a)` — the two forms every SVG consumer (browsers, resvg,\n * librsvg, Inkscape) agrees on.\n */\nexport function formatCssColor(c: RgbaColor): string {\n const r = clampByte(c.r)\n const g = clampByte(c.g)\n const b = clampByte(c.b)\n const a = Math.min(1, Math.max(0, c.a))\n if (a >= 1) {\n return '#' + [r, g, b].map((n) => n.toString(16).padStart(2, '0')).join('')\n }\n return `rgba(${r}, ${g}, ${b}, ${Number(a.toFixed(3))})`\n}\n","// ============================================================================\n// zombie-mermaid — shared statement splitting\n//\n// Every parser entry point (flowchart/state in src/parser.ts, the four\n// specialized parsers dispatched from src/index.ts and src/ascii/index.ts)\n// needs the same thing: turn raw Mermaid source into a list of trimmed,\n// comment-free statements. They each used to do it inline with\n// `.split('\\n').map(trim).filter(...)`, which meant semicolon separators —\n// valid Mermaid, and the form `detectDiagramType` already assumed when\n// isolating the header — were understood by nobody.\n//\n// This module is the single source of truth for \"what counts as one\n// statement\".\n// ============================================================================\n\n/**\n * Matches a character reference in either spelling Mermaid accepts.\n *\n * HTML/XML form: `&`, `#`, `😀`.\n * Mermaid's own form, which uses `#` where HTML uses `&`: `#59;`, `#9829;`,\n * `#quot;` — the documented way to put a literal semicolon, quote, or symbol\n * into a label.\n *\n * Both end in a semicolon that belongs to the reference, not to statement\n * separation. Splitting one apart corrupts the label *and* invents a\n * statement out of the remainder.\n */\nconst ENTITY_AT = /^[&#](?:#?[0-9]+|#?[xX][0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/\n\n/** Characters that can open a character reference. */\nconst ENTITY_STARTERS = new Set(['&', '#'])\n\n/**\n * Index of the `%%` that starts a comment on this line, or -1.\n *\n * A Mermaid comment runs to the end of the line, so everything after it —\n * including any further `;`-separated fragments — is commented out. Scanning\n * for it before splitting is what makes that true: dropping fragments that\n * merely *begin* with `%%` afterwards leaves `graph TD; A-->B; %% note; C-->D`\n * still parsing `C-->D` as code.\n *\n * Quote- and reference-aware for the same reasons the splitter is, so a `%%`\n * inside a label is text rather than a comment marker.\n */\nfunction commentStart(line: string): number {\n let quote: '\"' | \"'\" | null = null\n\n for (let i = 0; i < line.length; i++) {\n const ch = line[i]!\n\n if (quote !== null) {\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ENTITY_STARTERS.has(ch)) {\n const entity = ENTITY_AT.exec(line.slice(i))\n if (entity) {\n i += entity[0].length - 1\n continue\n }\n }\n if (ch === '%' && line[i + 1] === '%') return i\n }\n\n return -1\n}\n\n/**\n * Split one already-newline-separated line on its statement-separating\n * semicolons.\n *\n * A semicolon separates statements unless it is:\n * - inside a quoted string (`A[\"a; b\"]`), or\n * - the terminator of a character reference (`A[&]`).\n *\n * Quote tracking is deliberately simple — Mermaid has no escape sequence\n * inside quoted labels, so a quote character always opens or closes a span.\n */\nfunction splitOnSemicolons(line: string): string[] {\n const parts: string[] = []\n let current = ''\n let quote: '\"' | \"'\" | null = null\n\n for (let i = 0; i < line.length; i++) {\n const ch = line[i]!\n\n if (quote !== null) {\n current += ch\n if (ch === quote) quote = null\n continue\n }\n\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n current += ch\n continue\n }\n\n if (ENTITY_STARTERS.has(ch)) {\n // Copy a whole character reference in one go so its trailing ';' can't\n // be read as a separator. Guarded on '#' as well as '&': Mermaid's own\n // `#59;` spelling is far more common in diagrams than the HTML one.\n const entity = ENTITY_AT.exec(line.slice(i))\n if (entity) {\n current += entity[0]\n i += entity[0].length - 1\n continue\n }\n }\n\n if (ch === ';') {\n parts.push(current)\n current = ''\n continue\n }\n\n current += ch\n }\n\n parts.push(current)\n return parts\n}\n\n/**\n * One statement produced by the splitter, paired with the 1-based physical\n * source line it came from.\n *\n * `line` is captured while walking `text.split('\\n')`, before blank lines\n * and `%%` comment lines are dropped — so it survives being the original\n * source line number even though a statement's array index no longer does.\n * This is what lets every parser built on top of `splitStatements`/\n * `splitStatementsByLine` report *where* an error is, not just quote back\n * the offending statement's text (see issue #760).\n */\nexport interface Statement {\n text: string\n line: number\n}\n\n/**\n * Split Mermaid source into trimmed statements, grouped by the physical\n * source line each one came from — dropping blank lines and `%%` comment\n * lines.\n *\n * Newlines and semicolons both separate statements, matching Mermaid's own\n * `graph TD; A-->B;` form. Comments are removed *before* semicolon splitting\n * so that a `;` inside a comment can't resurrect the rest of that line as\n * code.\n *\n * The grouping is what lets a caller tell a genuine multi-line continuation\n * (crossing from one line's group into the next) apart from an explicit\n * `;`-separated statement on the *same* line (a later entry within one\n * group) — see `src/parser.ts`'s `mergeContinuationLines`, which only\n * treats the former as mergeable.\n */\nexport function splitStatementsByLine(text: string): Statement[][] {\n const groups: Statement[][] = []\n const rawLines = text.split('\\n')\n\n for (let lineIndex = 0; lineIndex < rawLines.length; lineIndex++) {\n let line = rawLines[lineIndex]!.trim()\n const lineNumber = lineIndex + 1\n\n /*\n * Cut the comment off first. A Mermaid comment runs to end of line, so\n * anything after `%%` is commented out — including further `;`-separated\n * fragments. Splitting first and discarding fragments that start with\n * `%%` would leave `A-->B; %% note; C-->D` parsing `C-->D` as code.\n */\n const comment = commentStart(line)\n if (comment !== -1) line = line.slice(0, comment).trim()\n if (line.length === 0) continue\n\n const statements: Statement[] = []\n for (const part of splitOnSemicolons(line)) {\n const statement = part.trim()\n if (statement.length === 0) continue\n statements.push({ text: statement, line: lineNumber })\n }\n if (statements.length > 0) groups.push(statements)\n }\n\n return groups\n}\n\n/**\n * Split Mermaid source into trimmed statements, dropping blank lines and\n * `%%` comment lines. See `splitStatementsByLine` for the line-grouped form\n * this flattens.\n */\nexport function splitStatements(text: string): Statement[] {\n return splitStatementsByLine(text).flat()\n}\n","// ============================================================================\n// zombie-mermaid — shared diagram-type detection\n//\n// Both the SVG renderer (src/index.ts) and the ASCII renderer\n// (src/ascii/index.ts) need to classify Mermaid source text before\n// dispatching to the right parse/layout/render pipeline. This module is the\n// single source of truth for that classification so the two backends can\n// never drift out of sync with each other again.\n// ============================================================================\n\nimport { splitStatements } from './statements.ts'\n\n/** The diagram types this library can detect and route to a renderer. */\nexport type DiagramType = 'flowchart' | 'sequence' | 'class' | 'er' | 'xychart'\n\n/**\n * Detect the diagram type from the mermaid source text.\n * Returns the type keyword used for routing to the correct pipeline.\n *\n * The header keyword is the first *statement*, not the first line: Mermaid\n * allows `;` as a statement separator, so `flowchart TD;A-->B` has its header\n * and first edge on one line. Uses the same splitter every parser uses, so\n * routing and parsing can never disagree about where the header ends.\n */\nexport function detectDiagramType(text: string): DiagramType {\n const firstLine = splitStatements(text)[0]?.text.toLowerCase() ?? ''\n\n if (/^xychart(?:-beta)?(?:\\s|$)/.test(firstLine)) return 'xychart'\n if (/^sequencediagram\\s*$/.test(firstLine)) return 'sequence'\n if (/^classdiagram\\s*$/.test(firstLine)) return 'class'\n if (/^erdiagram\\s*$/.test(firstLine)) return 'er'\n\n // Default: flowchart/state (handled by parseMermaid internally)\n return 'flowchart'\n}\n","// ============================================================================\n// Direction token guard\n//\n// Lives in `core` rather than beside the flowchart parser that used to own\n// it (`src/parser.ts`) because `direction-override.ts` — also `core`, and\n// reached from the ASCII entry — needs it. Left in the parser, `core` would\n// import `mermaid-parser`, i.e. the shared package would depend on one of\n// its own consumers (zombie-mermaid#625, umbrella #620).\n//\n// `toDirection` joined `isDirection` here under #624 for the identical\n// reason: it used to live in `src/parser.ts` too, with that file as its\n// only caller, until splitting `er/parser.ts` out into\n// `packages/mermaid-parser/` gave it a second caller that cannot import the\n// umbrella's `src/parser.ts` without creating the same cycle `isDirection`\n// was moved to avoid. `src/parser.ts` now imports it from here instead of\n// defining it.\n//\n// `Direction` is only a compile-time guarantee, so every entry point that\n// accepts a direction from outside (`RenderOptions.direction`, the CLI's\n// `--direction` flag, a parsed `graph LR` header) re-checks it at runtime\n// through this guard.\n// ============================================================================\n\nimport type { Direction } from './types.ts'\n\n/** True when `value` is one of the five directions Mermaid recognizes. */\nexport function isDirection(value: string): value is Direction {\n return (\n value === 'TD' ||\n value === 'TB' ||\n value === 'LR' ||\n value === 'BT' ||\n value === 'RL'\n )\n}\n\n/**\n * Normalize a regex-captured direction token to a `Direction`.\n *\n * Accepts `string | undefined` because every call site passes a regex\n * capture group value (`match[1]`, typed as possibly-`undefined` under\n * `noUncheckedIndexedAccess`) straight through. Each call site's regex\n * guards the capture with the same `(TD|TB|LR|BT|RL)` alternation\n * (case-insensitively), so the group always participates in the match in\n * practice — but that's a regex-structure guarantee the type checker can't\n * see across the call site. Validating `undefined` here, instead of\n * asserting non-null at each call site, turns a hypothetical violation into\n * a clear, descriptive error rather than a raw `undefined` crash.\n */\nexport function toDirection(raw: string | undefined): Direction {\n const upper = raw?.toUpperCase()\n if (upper === undefined || !isDirection(upper)) {\n throw new Error(`Invalid direction: \"${raw}\"`)\n }\n return upper\n}\n","// ============================================================================\n// Render-time direction override (issue #276)\n//\n// `RenderOptions.direction` / `AsciiRenderOptions.direction` replace the\n// *top-level* direction a diagram declared in its source (a flowchart's\n// `graph LR` header, a state diagram's or ER diagram's `direction LR`\n// line) after parsing and before layout. Nothing here touches the source\n// text, and nothing here touches a nested subgraph's or composite state's\n// own `direction` — those live on the subgraph objects, not on the field\n// replaced below, so they keep winning locally over the top-level\n// direction exactly as they do relative to the diagram's own header.\n//\n// Shared by the SVG entry (src/index.ts) and the ASCII entry\n// (src/ascii/index.ts). Deliberately dependency-free beyond the\n// `isDirection` guard so the `zombie-mermaid/ascii` subpath stays free of\n// elkjs — the guard itself moved out of `src/parser.ts` and into this\n// package (see `direction.ts`) so that staying dependency-free survives\n// the workspace split (zombie-mermaid#625).\n// ============================================================================\n\nimport type { Direction } from './types.ts'\nimport { isDirection } from './direction.ts'\n\n/**\n * Return `diagram` with its top-level `direction` replaced by `override`,\n * or `diagram` itself (same reference) when there is nothing to apply.\n *\n * The parsed diagram is never mutated — a shallow clone carries the new\n * direction, so `parseMermaid()` output a caller holds on to stays exactly\n * what the parser produced, and a second render of the same parsed object\n * without the option sees the source direction again.\n *\n * `isDirection` re-checks the value at runtime because `Direction` is only\n * a compile-time guarantee: a plain-JavaScript caller can pass any string.\n * An unrecognized value is ignored (the diagram's own direction stands)\n * rather than thrown on — the same lenient treatment every other\n * `RenderOptions` value gets, e.g. an unknown `interactivity` or `curve`\n * string falls back to its default instead of failing the render.\n */\nexport function withDirectionOverride<T extends { direction?: Direction }>(\n diagram: T,\n override: Direction | undefined,\n): T {\n if (override === undefined || !isDirection(override)) return diagram\n return { ...diagram, direction: override }\n}\n","// ============================================================================\n// zombie-mermaid — %%{init: ...}%% configuration directives\n//\n// Mermaid lets a diagram carry its own configuration inline:\n//\n// %%{init: {\"theme\": \"dark\", \"flowchart\": {\"curve\": \"basis\"}}}%%\n// flowchart TD\n// A --> B\n//\n// The payload is JSON-ish: Mermaid accepts unquoted keys and single quotes,\n// which strict JSON.parse rejects, so it is normalized before parsing.\n//\n// A directive only ever *supplies defaults*. Explicit render options passed\n// by the caller win, because the caller is closer to the user's intent than\n// text embedded in a diagram — and because a diagram from an untrusted source\n// should not be able to override the host application's rendering choices.\n// ============================================================================\n\nimport type { RenderOptions } from './types.ts'\n\n/** How an edge path is interpolated between its routed points. */\nexport type CurveStyle =\n 'linear' | 'basis' | 'natural' | 'step' | 'stepBefore' | 'stepAfter'\n\nconst CURVE_STYLES = new Set<CurveStyle>([\n 'linear',\n 'basis',\n 'natural',\n 'step',\n 'stepBefore',\n 'stepAfter',\n])\n\n/** Configuration a diagram can set for itself via `%%{init: ...}%%`. */\nexport interface InitConfig {\n theme?: string\n /** Flowchart edge interpolation. */\n curve?: CurveStyle\n /** Recognized but not acted on — see `IGNORED_KEYS`. */\n ignored: string[]\n}\n\n/**\n * Directive keys that are parsed and deliberately not acted on, with the\n * reason. Reported on the result so a caller can surface them rather than\n * having the setting vanish silently.\n */\nconst IGNORED_KEYS: Record<string, string> = {\n theme:\n \"colors come from the caller's bg/fg render options, which are usually CSS variables so a diagram inherits the host page's light/dark. A diagram-supplied theme would hard-code colors that fight it. Mermaid's theme names (default/dark/forest/neutral) also have no equivalent in this renderer's palettes — pass `bg`/`fg`, or a THEMES entry, instead\",\n securitylevel:\n 'this renderer emits static SVG and never executes diagram-supplied script, so there is no sandbox to configure',\n defaultrenderer:\n 'ELK is the only layout engine; dagre/elk selection has no effect',\n fontfamily: 'use the `font` render option instead',\n htmllabels:\n 'labels are always rendered as SVG text; there is no HTML label mode',\n maxtextsize: 'no text-size limit is enforced',\n startonload: 'not a browser auto-render integration',\n}\n\n/**\n * Matches a `%%{init: ... }%%` or `%%{initialize: ... }%%` directive.\n *\n * The body is captured lazily up to the closing `}%%` so a directive sharing\n * a line with other content does not swallow it.\n */\nconst INIT_DIRECTIVE = /^\\s*%%\\{\\s*(?:init|initialize)\\s*:\\s*([\\s\\S]*?)\\}%%/i\n\n/** True if `line` is an init directive. */\nexport function isInitDirective(line: string): boolean {\n return INIT_DIRECTIVE.test(line)\n}\n\n/**\n * Turn Mermaid's relaxed JSON into strict JSON.\n *\n * Mermaid accepts `{theme: 'dark'}`; JSON.parse does not. Quoting is applied\n * only outside string literals so a value containing a colon, a brace, or an\n * apostrophe survives intact.\n */\nfunction normalizeRelaxedJson(text: string): string {\n let out = ''\n let quote: '\"' | \"'\" | null = null\n\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]!\n\n if (quote !== null) {\n if (ch === '\\\\' && i + 1 < text.length) {\n /*\n * Consume the escape and its target together. Without this, the\n * backslash is emitted bare and the quote it protects reads as the\n * end of the string — so strict, valid JSON like {\"theme\": \"a\\\"b\"}\n * re-emits unbalanced and the whole directive is dropped.\n *\n * JSON has no \\' escape, so a single-quoted string's \\' becomes a\n * bare apostrophe. Every other escape passes through untouched.\n */\n const next = text[i + 1]!\n out += next === \"'\" ? \"'\" : `\\\\${next}`\n i++\n } else if (ch === quote) {\n quote = null\n out += '\"'\n } else if (ch === '\"') {\n // A double quote inside a single-quoted string must be escaped once\n // the string is re-emitted with double quotes.\n out += '\\\\\"'\n } else {\n out += ch\n }\n continue\n }\n\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n out += '\"'\n continue\n }\n\n // Bare key: an identifier run followed (after spaces) by a colon.\n if (/[A-Za-z_$]/.test(ch)) {\n let j = i\n while (j < text.length && /[\\w$-]/.test(text[j]!)) j++\n const word = text.slice(i, j)\n let k = j\n while (k < text.length && /\\s/.test(text[k]!)) k++\n\n if (text[k] === ':') {\n out += `\"${word}\"`\n i = j - 1\n continue\n }\n\n // A bare word in value position — `true`, `false`, `null` are already\n // valid JSON; anything else is quoted so it parses as a string.\n out += /^(true|false|null)$/i.test(word)\n ? word.toLowerCase()\n : `\"${word}\"`\n i = j - 1\n continue\n }\n\n out += ch\n }\n\n return out\n}\n\n/**\n * Parse an init directive's payload.\n *\n * Returns `undefined` when the line is not a directive or its payload cannot\n * be parsed. A malformed directive is ignored rather than fatal: Mermaid\n * treats config as advisory, and failing an entire diagram over a stray brace\n * in a comment-like construct would be a poor trade.\n */\nexport function parseInitDirective(line: string): InitConfig | undefined {\n const match = line.match(INIT_DIRECTIVE)\n if (!match) return undefined\n\n let payload: unknown\n try {\n payload = JSON.parse(normalizeRelaxedJson(match[1]!))\n } catch {\n return undefined\n }\n\n if (typeof payload !== 'object' || payload === null) return undefined\n\n const config: InitConfig = { ignored: [] }\n const record: Record<string, unknown> = { ...payload }\n\n for (const [key, value] of Object.entries(record)) {\n const lower = key.toLowerCase()\n\n if (lower === 'theme' && typeof value === 'string') {\n /*\n * Kept on the result as metadata for a caller that wants to act on it,\n * but listed as ignored: it is parsed and deliberately not applied. See\n * IGNORED_KEYS for why.\n */\n config.theme = value\n config.ignored.push(key)\n continue\n }\n\n if (lower === 'flowchart' && typeof value === 'object' && value !== null) {\n const flowchart: Record<string, unknown> = { ...value }\n for (const [fk, fv] of Object.entries(flowchart)) {\n if (fk.toLowerCase() === 'curve' && typeof fv === 'string') {\n if (CURVE_STYLES.has(fv as CurveStyle)) {\n config.curve = fv as CurveStyle\n }\n continue\n }\n if (fk.toLowerCase() in IGNORED_KEYS) config.ignored.push(fk)\n }\n continue\n }\n\n if (lower in IGNORED_KEYS) config.ignored.push(key)\n }\n\n return config\n}\n\n/**\n * Scan a whole diagram for init directives and merge them.\n *\n * Later directives win over earlier ones, matching Mermaid, where a second\n * directive overrides the first.\n */\nexport function extractInitConfig(lines: string[]): InitConfig {\n const merged: InitConfig = { ignored: [] }\n\n for (const line of lines) {\n const config = parseInitDirective(line)\n if (!config) continue\n if (config.theme !== undefined) merged.theme = config.theme\n if (config.curve !== undefined) merged.curve = config.curve\n merged.ignored.push(...config.ignored)\n }\n\n return merged\n}\n\n/**\n * Fold an init config into render options.\n *\n * Caller-supplied options always win: a directive supplies a default, never\n * an override. See the module header for why.\n */\nexport function applyInitConfig(\n options: RenderOptions,\n config: InitConfig,\n): RenderOptions {\n if (config.curve === undefined || options.curve !== undefined) return options\n return { ...options, curve: config.curve }\n}\n\n/** Human-readable note about directive keys that were parsed but not applied. */\nexport function describeIgnored(config: InitConfig): string[] {\n return [...new Set(config.ignored)].map(\n (key) => `${key}: ${IGNORED_KEYS[key.toLowerCase()] ?? 'not supported'}`,\n )\n}\n","// ============================================================================\n// Text Metrics — Variable-width character measurement for SVG layout\n// ============================================================================\n//\n// Provides font-agnostic text width estimation using character class buckets.\n// More accurate than uniform character width for proportional fonts.\n//\n// Width ratios are normalized where 1.0 = average lowercase letter.\n// Final pixel width = sum(charWidths) * fontSize * baseRatio\n// ============================================================================\n\n/**\n * Exact advance widths for every printable ASCII character (U+0020–U+007E),\n * measured from the real Inter font (canvas measureText, headless Chrome,\n * weight 400) and normalized so that ratio × fontSize × baseRatio reproduces\n * the measured pixel advance. Completeness over this range is asserted by\n * the `getCharWidth` \"table completeness\" test in text-metrics.test.ts.\n *\n * The coarse character-class buckets below remain as fallback for\n * non-ASCII characters not in this table. The buckets systematically\n * underestimated Inter widths (spaces, m/w, digits, punctuation), and the\n * error grew with text length — wide edge labels overflowed their\n * 8px-padded background rects.\n */\nconst INTER_ADVANCES: Record<string, number> = {\n ' ': 0.521,\n '!': 0.533,\n '\"': 0.863,\n '#': 1.173,\n $: 1.188,\n '%': 1.818,\n '&': 1.193,\n \"'\": 0.555,\n '(': 0.675,\n ')': 0.675,\n '*': 0.928,\n '+': 1.225,\n ',': 0.534,\n '-': 0.852,\n '.': 0.534,\n '/': 0.667,\n '0': 1.168,\n '1': 0.753,\n '2': 1.129,\n '3': 1.144,\n '4': 1.196,\n '5': 1.099,\n '6': 1.148,\n '7': 1.048,\n '8': 1.146,\n '9': 1.148,\n ':': 0.534,\n ';': 0.559,\n '<': 1.225,\n '=': 1.225,\n '>': 1.225,\n '?': 0.947,\n '@': 1.789,\n A: 1.278,\n B: 1.212,\n C: 1.353,\n D: 1.336,\n E: 1.113,\n F: 1.093,\n G: 1.382,\n H: 1.376,\n I: 0.497,\n J: 1.057,\n K: 1.244,\n L: 1.047,\n M: 1.673,\n N: 1.395,\n O: 1.416,\n P: 1.183,\n Q: 1.416,\n R: 1.192,\n S: 1.188,\n T: 1.195,\n U: 1.378,\n V: 1.278,\n W: 1.825,\n X: 1.263,\n Y: 1.257,\n Z: 1.165,\n '[': 0.675,\n '\\\\': 0.667,\n ']': 0.675,\n '^': 0.873,\n _: 0.845,\n '`': 0.598,\n a: 1.04,\n b: 1.134,\n c: 1.058,\n d: 1.134,\n e: 1.08,\n f: 0.685,\n g: 1.136,\n h: 1.095,\n i: 0.448,\n j: 0.448,\n k: 1.016,\n l: 0.448,\n m: 1.622,\n n: 1.094,\n o: 1.11,\n p: 1.134,\n q: 1.134,\n r: 0.697,\n s: 0.977,\n t: 0.606,\n u: 1.095,\n v: 1.041,\n w: 1.515,\n x: 1.011,\n y: 1.041,\n z: 1.023,\n '{': 0.789,\n '|': 0.616,\n '}': 0.789,\n '~': 1.225,\n}\n\n/**\n * Whether `char` has an exact measured Inter advance in `INTER_ADVANCES`,\n * as opposed to falling through to a coarse character-class bucket or the\n * default width in `getCharWidth`. Exported only so tests can assert the\n * table covers every printable ASCII character (U+0020–U+007E) — a gap\n * here means a real character silently degrades to an approximate width.\n */\nexport function hasMeasuredAdvance(char: string): boolean {\n return Object.hasOwn(INTER_ADVANCES, char)\n}\n\n/**\n * Narrow characters - visually thin glyphs.\n * Note: '1' is included because in proportional fonts (like Inter), it's\n * significantly narrower than other digits which use tabular/uniform width.\n */\nconst NARROW_CHARS = new Set([\n 'i',\n 'l',\n 't',\n 'f',\n 'j',\n 'I',\n '1',\n '!',\n '|',\n '.',\n ',',\n ':',\n ';',\n \"'\",\n])\n\n/**\n * Wide characters - visually wide glyphs\n */\nconst WIDE_CHARS = new Set(['W', 'M', 'w', 'm', '@', '%'])\n\n/**\n * Very wide characters - widest Latin glyphs\n */\nconst VERY_WIDE_CHARS = new Set(['W', 'M'])\n\n/**\n * Semi-narrow punctuation - brackets and slashes are narrower than letters\n * but wider than narrow chars like dots/commas\n */\nconst SEMI_NARROW_PUNCT = new Set([\n '(',\n ')',\n '[',\n ']',\n '{',\n '}',\n '/',\n '\\\\',\n '-',\n '\"',\n '`',\n])\n\n/**\n * Check if a code point is a combining diacritical mark (zero-width overlay)\n */\nfunction isCombiningMark(code: number): boolean {\n // Combining Diacritical Marks: U+0300–U+036F\n // Combining Diacritical Marks Extended: U+1AB0–U+1AFF\n // Combining Diacritical Marks Supplement: U+1DC0–U+1DFF\n // Combining Diacritical Marks for Symbols: U+20D0–U+20FF\n // Combining Half Marks: U+FE20–U+FE2F\n return (\n (code >= 0x0300 && code <= 0x036f) ||\n (code >= 0x1ab0 && code <= 0x1aff) ||\n (code >= 0x1dc0 && code <= 0x1dff) ||\n (code >= 0x20d0 && code <= 0x20ff) ||\n (code >= 0xfe20 && code <= 0xfe2f)\n )\n}\n\n/**\n * Check if a code point is fullwidth (CJK, emoji, etc.)\n * These characters occupy approximately 2x the width of Latin letters.\n */\nfunction isFullwidth(code: number): boolean {\n // CJK Radicals Supplement: U+2E80–U+2EFF\n // Kangxi Radicals: U+2F00–U+2FDF\n // CJK Symbols and Punctuation: U+3000–U+303F\n // Hiragana: U+3040–U+309F\n // Katakana: U+30A0–U+30FF\n // Bopomofo: U+3100–U+312F\n // Hangul Compatibility Jamo: U+3130–U+318F\n // Kanbun: U+3190–U+319F\n // Bopomofo Extended: U+31A0–U+31BF\n // CJK Strokes: U+31C0–U+31EF\n // Katakana Phonetic Extensions: U+31F0–U+31FF\n // Enclosed CJK Letters and Months: U+3200–U+32FF\n // CJK Compatibility: U+3300–U+33FF\n // CJK Unified Ideographs Extension A: U+3400–U+4DBF\n // CJK Unified Ideographs: U+4E00–U+9FFF\n // Hangul Syllables: U+AC00–U+D7AF\n // CJK Compatibility Ideographs: U+F900–U+FAFF\n // Halfwidth and Fullwidth Forms (fullwidth part): U+FF00–U+FF60, U+FFE0–U+FFE6\n // CJK Unified Ideographs Extension B+: U+20000–U+2A6DF (and beyond)\n\n return (\n (code >= 0x1100 && code <= 0x115f) || // Hangul Jamo\n (code >= 0x2e80 && code <= 0x2eff) || // CJK Radicals Supplement\n (code >= 0x2f00 && code <= 0x2fdf) || // Kangxi Radicals\n (code >= 0x3000 && code <= 0x303f) || // CJK Symbols and Punctuation\n (code >= 0x3040 && code <= 0x309f) || // Hiragana\n (code >= 0x30a0 && code <= 0x30ff) || // Katakana\n (code >= 0x3100 && code <= 0x312f) || // Bopomofo\n (code >= 0x3130 && code <= 0x318f) || // Hangul Compatibility Jamo\n (code >= 0x3190 && code <= 0x31ff) || // Kanbun + extensions\n (code >= 0x3200 && code <= 0x33ff) || // Enclosed CJK + Compatibility\n (code >= 0x3400 && code <= 0x4dbf) || // CJK Extension A\n (code >= 0x4e00 && code <= 0x9fff) || // CJK Unified Ideographs\n (code >= 0xac00 && code <= 0xd7af) || // Hangul Syllables\n (code >= 0xf900 && code <= 0xfaff) || // CJK Compatibility Ideographs\n (code >= 0xff00 && code <= 0xff60) || // Fullwidth ASCII\n (code >= 0xffe0 && code <= 0xffe6) || // Fullwidth symbols\n code >= 0x20000 // CJK Extension B and beyond\n )\n}\n\n/**\n * Regex for emoji detection using Unicode property escapes.\n * Uses Emoji_Presentation and Extended_Pictographic (not just Emoji)\n * because \\p{Emoji} includes digits and # which we don't want as fullwidth.\n *\n * \\p{Extended_Pictographic} on its own is broader than \"renders wide\" — it\n * flags characters that are merely emoji-*capable* (could take a VS16\n * selector to become emoji), not characters that actually render wide by\n * default. Most such characters (e.g. ❤ U+2764) are still conventionally\n * treated as wide, so the OR is kept — except for the Geometric Shapes\n * block (U+25A0–U+25FF, see `isGeometricShapesTextDefault` below), which\n * this renderer itself uses for narrow box-drawing/arrowhead glyphs like\n * ▶ U+25B6 and ◀ U+25C0 (see src/ascii/sequence.ts); those render as a\n * single narrow column in every real terminal despite being flagged\n * Extended_Pictographic.\n */\nconst EMOJI_PRESENTATION_REGEX = /\\p{Emoji_Presentation}/u\nconst EXTENDED_PICTOGRAPHIC_REGEX = /\\p{Extended_Pictographic}/u\n\n/**\n * Geometric Shapes block (U+25A0–U+25FF). Characters here that also carry\n * Emoji_Presentation (e.g. ◽ ◾) are unaffected by this — they're already\n * caught by the Emoji_Presentation check in `isEmoji` before this applies.\n */\nfunction isGeometricShapesTextDefault(code: number): boolean {\n return code >= 0x25a0 && code <= 0x25ff\n}\n\n/**\n * Arrows-block glyphs that carry Extended_Pictographic (emoji-capable) but\n * no default Emoji_Presentation, and render as a single narrow column in\n * every real terminal — the same misclassification the Geometric Shapes\n * block above hits. Covers:\n *\n * - ↖ U+2196, ↗ U+2197, ↘ U+2198, ↙ U+2199 — drawn by the ASCII renderer\n * itself for diagonal edge-routing arrowheads\n * (packages/ascii-renderer/src/draw-arrows.ts's `drawArrowHead`) in place\n * of the filled triangles (◢◣◤◥) JetBrains Mono NL has no glyph for at\n * all — see issue #1062.\n * - ↔ U+2194, ↕ U+2195, ↩ U+21A9, ↪ U+21AA — not drawn by the renderer\n * itself, but reachable if a diagram author types one into node/edge/note\n * label text, which passes through to the ASCII grid verbatim.\n */\nfunction isArrowsBlockTextDefault(code: number): boolean {\n return (\n (code >= 0x2194 && code <= 0x2199) || code === 0x21a9 || code === 0x21aa\n )\n}\n\n/**\n * Check if a character is an emoji (fullwidth)\n */\nfunction isEmoji(char: string): boolean {\n if (EMOJI_PRESENTATION_REGEX.test(char)) return true\n const code = char.codePointAt(0)\n if (code !== undefined && isGeometricShapesTextDefault(code)) return false\n if (code !== undefined && isArrowsBlockTextDefault(code)) return false\n return EXTENDED_PICTOGRAPHIC_REGEX.test(char)\n}\n\n/**\n * Check if a single character (code point) is \"wide\" — i.e. renders as two\n * columns in a monospace terminal / two grid cells wide instead of one.\n * Covers CJK/kana/hangul/fullwidth-form characters (via `isFullwidth`) as\n * well as emoji. Shared by the SVG text-measurement path (`getCharWidth`\n * above) and the ASCII grid's display-width helpers\n * (`src/ascii/display-width.ts`), so both agree on what counts as wide.\n */\nexport function isWideChar(char: string): boolean {\n const code = char.codePointAt(0)\n if (code === undefined) return false\n return isFullwidth(code) || isEmoji(char)\n}\n\n/**\n * Get the relative width of a single character.\n *\n * Returns a normalized width ratio where:\n * - 0.0 = zero-width (combining marks)\n * - 0.3 = space\n * - 0.4 = narrow (i, l, t, f, j, I, 1)\n * - 0.8 = semi-narrow (r)\n * - 1.0 = average lowercase\n * - 1.2 = wide lowercase / uppercase\n * - 1.5 = very wide (W, M)\n * - 2.0 = fullwidth (CJK, emoji)\n */\nexport function getCharWidth(char: string): number {\n const code = char.codePointAt(0)\n if (code === undefined) return 0\n\n // Exact measured advance for printable ASCII (Inter). The own-property\n // check must come first: without it, bracket access on a plain object\n // literal also resolves inherited Object.prototype properties (e.g.\n // `INTER_ADVANCES['toString']` is a function, not undefined).\n if (Object.hasOwn(INTER_ADVANCES, char)) {\n const measured = INTER_ADVANCES[char]\n // The `undefined` case is unreachable: INTER_ADVANCES is a `Record<string,\n // number>` object literal whose every value is a numeric literal, and\n // `noUncheckedIndexedAccess` is what forces this check's type, not any\n // real possibility that an own property here holds `undefined`. Given\n // `Object.hasOwn` is already true, `measured` is always a defined\n // number — kept (not simplified to a bare `return measured`) so the\n // check still holds if a future entry's value type ever widens.\n /* v8 ignore else */\n if (measured !== undefined) return measured\n }\n\n // Zero-width: combining diacritical marks\n if (isCombiningMark(code)) return 0\n\n // Fullwidth: CJK, emoji\n if (isFullwidth(code) || isEmoji(char)) return 2.0\n\n // Accented Latin: use the base letter's measured advance (é → e, Ü → U)\n const base = char.normalize('NFD')[0]\n if (base !== undefined && base !== char) {\n const baseMeasured = INTER_ADVANCES[base]\n if (baseMeasured !== undefined) return baseMeasured\n }\n\n // Space\n if (char === ' ') return 0.3\n\n // Very wide Latin\n if (VERY_WIDE_CHARS.has(char)) return 1.5\n\n // Wide Latin\n if (WIDE_CHARS.has(char)) return 1.2\n\n // Narrow Latin\n if (NARROW_CHARS.has(char)) return 0.4\n\n // Semi-narrow punctuation (brackets, slashes, hyphens)\n if (SEMI_NARROW_PUNCT.has(char)) return 0.5\n\n // Semi-narrow letter\n if (char === 'r') return 0.8\n\n // Uppercase (slightly wider than lowercase on average)\n if (code >= 65 && code <= 90) return 1.2\n\n // Digits (uniform width in most fonts)\n if (code >= 48 && code <= 57) return 1.0\n\n // Default: average lowercase width\n return 1.0\n}\n\n/**\n * Measure the pixel width of a text string.\n *\n * Uses character class buckets for more accurate width estimation\n * than uniform character width assumptions.\n *\n * @param text - The text to measure\n * @param fontSize - Font size in pixels\n * @param fontWeight - Font weight (affects width slightly)\n * @returns Estimated width in pixels\n */\n/** Advance width of a monospace glyph, as a fraction of font size. */\nconst MONO_ADVANCE = 0.6\n\n/**\n * Whether text is measured with monospace metrics.\n *\n * The character buckets above model a proportional face (they are calibrated for Inter), but\n * `RenderOptions.font` accepts any family. Measuring a monospace font with them sizes every box\n * for the wrong glyph widths: narrow strings under-measure by ~60% and overflow, wide ones\n * over-measure by ~37%.\n *\n * The switch lives here rather than in `styles.ts` because this is the single choke point every\n * diagram type measures through — `estimateTextWidth` and `measureMultilineText` both land here,\n * and flowchart layout only reaches text metrics via the latter.\n */\nlet monospaceMetrics = false\n\n/** Fonts whose glyphs all share one advance width. */\nexport function isMonospaceFont(font: string): boolean {\n // `mono` unanchored so it also catches `ui-monospace`; `code` bounded so it does not fire on\n // unrelated names. \"Mona Sans\" is deliberately not a match.\n return /mono|consol|menlo|courier|\\bcode\\b/i.test(font)\n}\n\n/** Select the metrics model for subsequent measurements. Called once per render. */\nexport function setMonospaceMetrics(monospace: boolean): void {\n monospaceMetrics = monospace\n}\n\nexport function measureTextWidth(\n text: string,\n fontSize: number,\n fontWeight: number,\n): number {\n // Add minimum padding to prevent truncation at text boundaries\n // Increased from 0.1 to 0.15 for better label separation and collision prevention\n const minPadding = fontSize * 0.15\n\n if (monospaceMetrics) {\n let count = 0\n // Count code points, so surrogate pairs stay one cell wide.\n for (const _ of text) count += 1\n return count * fontSize * MONO_ADVANCE + minPadding\n }\n\n // Base ratio calibrated for Inter font family\n // Heavier weights are slightly wider\n // Added +0.02 buffer to prevent edge truncation of characters like 's' at line ends\n const baseRatio = fontWeight >= 600 ? 0.6 : fontWeight >= 500 ? 0.57 : 0.54\n\n let totalWidth = 0\n\n // Iterate over code points (handles surrogate pairs for emoji/CJK)\n for (const char of text) {\n totalWidth += getCharWidth(char)\n }\n\n return totalWidth * fontSize * baseRatio + minPadding\n}\n\n// ============================================================================\n// Multi-line Text Measurement\n// ============================================================================\n\n/** Standard line height ratio for multi-line text (1.3 = 130% of font size) */\nexport const LINE_HEIGHT_RATIO = 1.3\n\n/** Metrics for multi-line text measurement */\nexport interface MultilineMetrics {\n /** Maximum line width in pixels */\n width: number\n /** Total height in pixels (lines × lineHeight) */\n height: number\n /** Individual lines after splitting */\n lines: string[]\n /** Computed line height in pixels */\n lineHeight: number\n}\n\n/**\n * Measure multi-line text dimensions.\n *\n * Splits text on newlines and returns the maximum width across all lines,\n * total height based on line count, and the split lines for rendering.\n *\n * @param text - The text to measure (may contain \\n)\n * @param fontSize - Font size in pixels\n * @param fontWeight - Font weight (affects width slightly)\n * @returns Metrics including width, height, lines array, and lineHeight\n */\nexport function measureMultilineText(\n text: string,\n fontSize: number,\n fontWeight: number,\n): MultilineMetrics {\n const lines = text.split('\\n')\n const lineHeight = fontSize * LINE_HEIGHT_RATIO\n\n // Width = max of all line widths\n let maxWidth = 0\n for (const line of lines) {\n const plain = line.replace(/<\\/?(?:b|strong|i|em|u|s|del)\\s*>/gi, '')\n const w = measureTextWidth(plain, fontSize, fontWeight)\n if (w > maxWidth) maxWidth = w\n }\n\n return {\n width: maxWidth,\n height: lines.length * lineHeight,\n lines,\n lineHeight,\n }\n}\n","// ============================================================================\n// Multi-line Text Rendering Utilities\n//\n// Shared utilities for rendering multi-line text in SVG using <tspan> elements.\n// Supports inline formatting: <b>, <i>, <u>, <s> mapped to SVG attributes.\n// Used across all diagram types (flowcharts, state, sequence, class, ER).\n// ============================================================================\n\nimport { LINE_HEIGHT_RATIO } from './text-metrics.ts'\n\n/**\n * Normalize label text: strip surrounding quotes, convert <br> tags and\n * literal \\n sequences to newline characters. Strips unsupported HTML tags\n * but preserves formatting tags (<b>, <i>, <u>, <s>) for SVG rendering.\n */\nexport function normalizeBrTags(label: string): string {\n // Strip surrounding double quotes (Mermaid uses them for special chars in labels)\n const unquoted =\n label.startsWith('\"') && label.endsWith('\"') ? label.slice(1, -1) : label\n\n /*\n * Mermaid's markdown-string form wraps the label in backticks —\n * `A[\"` + '`**bold**`' + `\"]`. The markdown conversion below already runs\n * unconditionally, so the backticks only need removing; left in place they\n * rendered as literal characters in the label.\n */\n const unfenced =\n unquoted.length >= 2 && unquoted.startsWith('`') && unquoted.endsWith('`')\n ? unquoted.slice(1, -1)\n : unquoted\n\n return (\n unfenced\n .replace(/<br\\s*\\/?>/gi, '\\n')\n .replace(/\\\\n/g, '\\n')\n .replace(/<\\/?(?:sub|sup|small|mark)\\s*>/gi, '')\n // Markdown formatting → HTML tags (order matters: ** before *)\n .replace(/\\*\\*(.+?)\\*\\*/g, '<b>$1</b>')\n .replace(/(?<!\\*)\\*([^\\s*](?:[^*]*[^\\s*])?)\\*(?!\\*)/g, '<i>$1</i>')\n .replace(/~~(.+?)~~/g, '<s>$1</s>')\n )\n}\n\n/**\n * Strip all inline formatting tags from text, keeping only plain text.\n * Used for text measurement where tag characters shouldn't affect width.\n */\nexport function stripFormattingTags(text: string): string {\n return text.replace(/<\\/?(?:b|strong|i|em|u|s|del)\\s*>/gi, '')\n}\n\n/**\n * Escape special XML characters in text content.\n */\nexport function escapeXml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n/**\n * Escape a string for use as an XML/HTML attribute value.\n * Escapes quotes and ampersands to prevent attribute injection.\n *\n * Deliberately distinct from `escapeXml()` above: attribute values in this\n * codebase are always double-quoted, so single quotes don't need escaping —\n * `escapeXml()` escapes them too (for safe embedding in either quote style),\n * which would change output if reused here.\n */\nexport function escapeAttr(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\n// ============================================================================\n// Inline formatting: <b>, <i>, <u>, <s> → SVG tspan attributes\n// ============================================================================\n\ninterface StyledSegment {\n text: string\n bold: boolean\n italic: boolean\n underline: boolean\n strikethrough: boolean\n}\n\n/** Regex to match opening/closing formatting tags */\nconst FORMAT_TAG_REGEX = /<(\\/)?(?:(b|strong)|(i|em)|(u)|(s|del))\\s*>/gi\n\n/**\n * Parse a line of text into styled segments based on inline formatting tags.\n * Supports nesting: `<b>bold <i>both</i> bold</b>`.\n */\nfunction parseInlineFormatting(line: string): StyledSegment[] {\n const segments: StyledSegment[] = []\n let bold = false,\n italic = false,\n underline = false,\n strikethrough = false\n let lastIndex = 0\n\n // Reset lastIndex for global regex\n FORMAT_TAG_REGEX.lastIndex = 0\n\n let match: RegExpExecArray | null\n while ((match = FORMAT_TAG_REGEX.exec(line)) !== null) {\n // Capture text before this tag\n if (match.index > lastIndex) {\n segments.push({\n text: line.slice(lastIndex, match.index),\n bold,\n italic,\n underline,\n strikethrough,\n })\n }\n lastIndex = match.index + match[0].length\n\n const isClosing = Boolean(match[1])\n // match[2] = b|strong, match[3] = i|em, match[4] = u, match[5] = s|del\n if (match[2]) bold = !isClosing\n else if (match[3]) italic = !isClosing\n else if (match[4]) underline = !isClosing\n else if (match[5]) strikethrough = !isClosing\n }\n\n // Remaining text after last tag\n if (lastIndex < line.length) {\n segments.push({\n text: line.slice(lastIndex),\n bold,\n italic,\n underline,\n strikethrough,\n })\n }\n\n return segments\n}\n\n/** Check if a line contains any formatting tags */\nconst HAS_FORMAT_TAGS = /<\\/?(?:b|strong|i|em|u|s|del)\\s*>/i\n\n/**\n * Render a line's content as SVG, with inline formatting applied as tspan attributes.\n * Returns raw SVG content (no wrapping tspan — caller provides positioning).\n */\nfunction renderLineContent(line: string): string {\n // Fast path: no formatting tags\n if (!HAS_FORMAT_TAGS.test(line)) return escapeXml(line)\n\n const segments = parseInlineFormatting(line)\n if (segments.length === 0) return ''\n\n // If all segments are unstyled, just escape\n const allPlain = segments.every(\n (s) => !s.bold && !s.italic && !s.underline && !s.strikethrough,\n )\n if (allPlain) return segments.map((s) => escapeXml(s.text)).join('')\n\n return segments\n .map((seg) => {\n const escaped = escapeXml(seg.text)\n if (!seg.bold && !seg.italic && !seg.underline && !seg.strikethrough)\n return escaped\n\n const attrs: string[] = []\n if (seg.bold) attrs.push('font-weight=\"bold\"')\n if (seg.italic) attrs.push('font-style=\"italic\"')\n // SVG text-decoration can combine values\n const deco: string[] = []\n if (seg.underline) deco.push('underline')\n if (seg.strikethrough) deco.push('line-through')\n if (deco.length) attrs.push(`text-decoration=\"${deco.join(' ')}\"`)\n\n return `<tspan ${attrs.join(' ')}>${escaped}</tspan>`\n })\n .join('')\n}\n\n// ============================================================================\n// Multi-line text rendering\n// ============================================================================\n\n/**\n * Render a multi-line text element with proper vertical centering.\n *\n * For single-line text, returns a simple <text> element.\n * For multi-line text (containing \\n), returns <text> with <tspan> children.\n * Inline formatting tags (<b>, <i>, <u>, <s>) are rendered as SVG attributes.\n *\n * @param text - The text to render (may contain \\n and formatting tags)\n * @param cx - Center x coordinate\n * @param cy - Center y coordinate\n * @param fontSize - Font size in pixels\n * @param attrs - Additional SVG attributes (e.g., 'text-anchor=\"middle\" fill=\"var(--_text)\"')\n * @param baselineShift - Baseline shift for vertical alignment (default 0.35)\n * @returns SVG text element string\n */\nexport function renderMultilineText(\n text: string,\n cx: number,\n cy: number,\n fontSize: number,\n attrs: string,\n baselineShift: number = 0.35,\n): string {\n const lines = text.split('\\n')\n\n // Single line — simple text element\n if (lines.length === 1) {\n const dy = fontSize * baselineShift\n return `<text x=\"${cx}\" y=\"${cy}\" ${attrs} dy=\"${dy}\">${renderLineContent(text)}</text>`\n }\n\n // Multi-line — use tspan elements with vertical centering\n const lineHeight = fontSize * LINE_HEIGHT_RATIO\n // First line dy: shift up by (n-1)/2 line heights, then add baseline shift\n const firstDy =\n -((lines.length - 1) / 2) * lineHeight + fontSize * baselineShift\n\n const tspans = lines\n .map((line, i) => {\n const dy = i === 0 ? firstDy : lineHeight\n return `<tspan x=\"${cx}\" dy=\"${dy}\">${renderLineContent(line)}</tspan>`\n })\n .join('')\n\n return `<text x=\"${cx}\" y=\"${cy}\" ${attrs}>${tspans}</text>`\n}\n\n/**\n * Render a multi-line text element with a background rectangle (pill).\n *\n * Used for edge labels that need a background for readability.\n *\n * @param text - The text to render (may contain \\n)\n * @param cx - Center x coordinate\n * @param cy - Center y coordinate\n * @param textWidth - Pre-calculated text width (max line width)\n * @param textHeight - Pre-calculated text height (lines × lineHeight)\n * @param fontSize - Font size in pixels\n * @param padding - Padding around text\n * @param textAttrs - SVG attributes for the text element\n * @param bgAttrs - SVG attributes for the background rect\n * @returns SVG elements string (rect + text)\n */\nexport function renderMultilineTextWithBackground(\n text: string,\n cx: number,\n cy: number,\n textWidth: number,\n textHeight: number,\n fontSize: number,\n padding: number,\n textAttrs: string,\n bgAttrs: string,\n): string {\n const bgWidth = textWidth + padding * 2\n const bgHeight = textHeight + padding * 2\n\n const rect =\n `<rect x=\"${cx - bgWidth / 2}\" y=\"${cy - bgHeight / 2}\" ` +\n `width=\"${bgWidth}\" height=\"${bgHeight}\" ${bgAttrs} />`\n\n const textEl = renderMultilineText(text, cx, cy, fontSize, textAttrs)\n\n return `${rect}\\n${textEl}`\n}\n","// ============================================================================\n// Style directives — `classDef`, `class A,B name`, `cssClass \"A,B\" name`,\n// `style A ...`, and the `A:::name` shorthand.\n//\n// Mermaid's flowchart and class diagrams share one styling grammar\n// (https://mermaid.js.org/syntax/flowchart.html#styling-and-classes,\n// https://mermaid.js.org/syntax/classDiagram.html#styling). The flowchart\n// parser grew the first implementation; this module is that implementation\n// lifted out so the class-diagram parser can reuse it rather than copy it\n// (issue #420). Both parsers feed a `StyleDirectives` bag, and both layouts\n// resolve a node's final style through `resolveNodeStyle` with the same\n// cascade — so a `classDef default` means the same thing in every diagram\n// type that supports it.\n// ============================================================================\n\n/** The three maps a styling-aware diagram carries. `MermaidGraph` and\n * `ClassDiagram` both satisfy this structurally. */\nexport interface StyleDirectives {\n /** Maps class names to their style properties (from `classDef name prop:val`) */\n classDefs: Map<string, Record<string, string>>\n /** Maps node IDs to their assigned class name (from `class A,B name`, `cssClass \"A,B\" name`, or `A:::name`) */\n classAssignments: Map<string, string>\n /** Maps node IDs to inline styles (from `style X fill:#f00,stroke:#333`) */\n nodeStyles: Map<string, Record<string, string>>\n}\n\n/** Parse \"fill:#f00,stroke:#333\" style property strings into a Record */\nexport function parseStyleProps(propsStr: string): Record<string, string> {\n // Strip trailing semicolons — Mermaid tolerates them (e.g. `stroke:#f00;`)\n const cleaned = propsStr.replace(/;\\s*$/, '')\n const props: Record<string, string> = {}\n for (const pair of cleaned.split(',')) {\n const colonIdx = pair.indexOf(':')\n if (colonIdx > 0) {\n const key = pair.slice(0, colonIdx).trim()\n const val = pair.slice(colonIdx + 1).trim()\n if (key && val) {\n props[key] = val\n }\n }\n }\n return props\n}\n\n/**\n * `classDef name prop:val,prop:val` — define a named style class.\n *\n * Mermaid's `classList` rule accepts a comma-separated list of names\n * (`classDef a,b font-size:12pt`) in both the flowchart and class grammars;\n * every name in the list gets the same properties.\n *\n * Returns true when `line` was a classDef statement (and has been applied).\n */\nexport function tryApplyClassDef(\n line: string,\n target: StyleDirectives,\n): boolean {\n const match = line.match(/^classDef\\s+([\\w,-]+)\\s+(.+)$/)\n if (!match) return false\n const props = parseStyleProps(match[2]!)\n for (const name of match[1]!.split(',')) {\n const trimmed = name.trim()\n if (trimmed) target.classDefs.set(trimmed, props)\n }\n return true\n}\n\n/**\n * `class A,B className` — attach a style class to one or more nodes.\n *\n * Allows an optional trailing semicolon (`class A,B foo;`) — Mermaid treats\n * it as valid/optional, and `classDef`/`style` already tolerate it via their\n * `(.+)$` capture. Without this, the semicolon form fails to match here and\n * falls through to node parsing, rendering a stray node labelled \"class\".\n *\n * Returns true when `line` was a class-assignment statement.\n */\nexport function tryApplyClassAssignment(\n line: string,\n target: StyleDirectives,\n): boolean {\n const match = line.match(/^class\\s+([\\w,-]+)\\s+([\\w-]+)\\s*;?\\s*$/)\n if (!match) return false\n const className = match[2]!\n for (const id of match[1]!.split(',')) {\n target.classAssignments.set(id.trim(), className)\n }\n return true\n}\n\n/**\n * `cssClass \"A,B\" className` — the class-diagram grammar's own attachment\n * form (`cssClassStatement: CSSCLASS STR ALPHA` in classDiagram.jison).\n * Same effect as `class A,B className`.\n *\n * Returns true when `line` was a cssClass statement.\n */\nexport function tryApplyCssClass(\n line: string,\n target: StyleDirectives,\n): boolean {\n const match = line.match(/^cssClass\\s+\"([^\"]*)\"\\s+([\\w-]+)\\s*;?\\s*$/)\n if (!match) return false\n const className = match[2]!\n for (const id of match[1]!.split(',')) {\n const trimmed = id.trim()\n if (trimmed) target.classAssignments.set(trimmed, className)\n }\n return true\n}\n\n/**\n * `style A,B fill:#f00,stroke:#333` — inline style on specific nodes.\n * Repeated `style` statements for the same node merge property by property.\n *\n * Returns true when `line` was a style statement.\n */\nexport function tryApplyStyleStatement(\n line: string,\n target: StyleDirectives,\n): boolean {\n const match = line.match(/^style\\s+([\\w,-]+)\\s+(.+)$/)\n if (!match) return false\n const props = parseStyleProps(match[2]!)\n for (const id of match[1]!.split(',').map((s) => s.trim())) {\n target.nodeStyles.set(id, { ...target.nodeStyles.get(id), ...props })\n }\n return true\n}\n\n/**\n * Split the `:::className` shorthand off a node/class identifier.\n *\n * `Animal:::someclass` → `{ id: 'Animal', className: 'someclass' }`;\n * an identifier without the shorthand comes back unchanged with no class.\n * Class names follow CSS identifier conventions (word characters and\n * hyphens), the same constraint the flowchart parser's CLASS_SHORTHAND_REGEX\n * applies.\n */\nexport function splitClassShorthand(identifier: string): {\n id: string\n className?: string\n} {\n const match = identifier.match(/^(.+?):::([\\w][\\w-]*)$/)\n if (!match) return { id: identifier }\n return { id: match[1]!, className: match[2]! }\n}\n\n/**\n * Resolve the final inline style for a node from classDefs and nodeStyles.\n *\n * Cascade, weakest to strongest:\n * 1. `classDef default` — Mermaid's implicit base for every node\n * 2. the node's own assigned class (`class A foo` / `A:::foo`)\n * 3. an explicit `style A ...` directive\n *\n * Returns undefined when nothing in the cascade applies, so callers can fall\n * back to theme defaults without an empty-object check.\n */\nexport function resolveNodeStyle(\n nodeId: string,\n directives: StyleDirectives,\n): Record<string, string> | undefined {\n let result: Record<string, string> | undefined\n\n /*\n * `classDef default` applies to every node without being assigned. It was\n * previously honored only when a node named it explicitly (`class X\n * default`), which silently diverges from Mermaid: a diagram styling all\n * its nodes via `classDef default` rendered unstyled with no error.\n */\n const defaultDef = directives.classDefs.get('default')\n if (defaultDef) {\n result = { ...defaultDef }\n }\n\n // Then the node's own class, overriding the default property by property.\n const className = directives.classAssignments.get(nodeId)\n if (className) {\n const classDef = directives.classDefs.get(className)\n if (classDef) {\n result = result ? { ...result, ...classDef } : { ...classDef }\n }\n }\n\n // Then, apply explicit style directives (override class styles)\n const nodeStyle = directives.nodeStyles.get(nodeId)\n if (nodeStyle) {\n result = result ? { ...result, ...nodeStyle } : { ...nodeStyle }\n }\n\n return result\n}\n\n/**\n * Validate a user-authored class name (from `:::className` or\n * `class A className`) before it's emitted into the SVG `class` attribute.\n *\n * The parsers already constrain class names to word characters and hyphens\n * (see `splitClassShorthand` above and the flowchart parser's\n * CLASS_SHORTHAND_REGEX), so this is a defense-in-depth allowlist rather\n * than an escaping step — a class name can't be made \"safe\" by escaping\n * since any character other than a valid CSS identifier character would\n * break the class token itself, not just the surrounding attribute quotes.\n * Anything that doesn't match a valid CSS identifier (letters, digits,\n * underscore, hyphen; not starting with a digit or a hyphen+digit) is\n * dropped rather than emitted.\n */\nexport function sanitizeClassName(\n className: string | undefined,\n): string | undefined {\n if (!className) return undefined\n return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(className) ? className : undefined\n}\n","/**\n * GENERATED FILE — do not edit by hand.\n *\n * Regenerate with `pnpm run build:mono-font`\n * (scripts/build-mono-font-subset.ts), which subsets\n * third_party/fonts/jetbrains-mono-nl/JetBrainsMonoNL-Regular.ttf down to\n * Basic Latin + Latin-1 Supplement — the self-hosted, embeddable\n * `@font-face` packages/core/src/theme.ts's `buildStyleBlock()` inlines\n * into an SVG's own `<style>` for its `.mono` rule (class-diagram method\n * signatures, ER-diagram attribute types), replacing a Google Fonts CDN\n * `@import` this library's SVG output previously depended on by default\n * (#1061). See that script's header comment for why this subset is\n * deliberately narrower than the site's own (demo/components/generated/\n * mono-font-subset.ts).\n */\n\n/** The self-hosted family name every consumer's font stack should list\n * first — matches this subset's own `name` table entry. */\nexport const MONO_FONT_FAMILY = 'JetBrains Mono NL'\n\n/** The `@font-face` rule embedding the subset as a base64 `woff2` data\n * URI — no separate network request, no page-depth-relative path to get\n * wrong (see the script header comment for why data-URI over a static\n * asset file). */\nexport const MONO_FONT_FACE_CSS = `@font-face {\n font-family: 'JetBrains Mono NL';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(data:font/woff2;base64,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) format('woff2');\n unicode-range: U+0020-007E, U+00A0-00FF;\n}`\n","// ============================================================================\n// Theme system — CSS custom property-based theming for mermaid SVG diagrams.\n//\n// Architecture:\n// - Two required variables: --bg (background) and --fg (foreground)\n// - Five optional enrichment variables: --line, --accent, --muted, --surface, --border\n// - Unset optionals fall back to color-mix() derivations from bg + fg\n// - All derived values computed in a <style> block inside the SVG\n//\n// This means the SVG is a function of its CSS variables. The caller provides\n// colors, and the SVG adapts. No light/dark mode detection needed.\n// ============================================================================\n\nimport { escapeXml, escapeAttr } from './multiline-utils.ts'\nimport { parseHexColor } from './color-utils.ts'\nimport {\n MONO_FONT_FAMILY as SVG_MONO_FONT_FAMILY,\n MONO_FONT_FACE_CSS as SVG_MONO_FONT_FACE_CSS,\n} from './generated/mono-font-subset.ts'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * How the renderers emit their two inline-style surfaces — the `<style>`\n * element(s) and the root `<svg style=\"--bg: …\">` attribute. Both exist so a\n * host page with a strict `Content-Security-Policy` (`style-src` without\n * `'unsafe-inline'`) can still render diagrams with their colours intact;\n * see `RenderOptions.nonce` / `RenderOptions.styleAttribute` in packages/core/src/types.ts\n * and GitHub issue #216. Threaded as one object through every diagram\n * renderer so the two options can't drift apart per diagram type.\n */\nexport interface SvgEmitOptions {\n /** Value for a `nonce` attribute on every emitted `<style>` element. */\n nonce?: string\n /** Emit the root `style=\"--bg: …\"` attribute. Default: true. */\n styleAttribute?: boolean\n}\n\n/**\n * Diagram color configuration.\n *\n * Required: bg + fg give you a clean mono diagram.\n * Optional: line, accent, muted, surface, border bring in richer color\n * from Shiki themes or custom palettes. Each falls back to a color-mix()\n * derivation from bg + fg if not set.\n */\nexport interface DiagramColors {\n /** Background color → CSS variable --bg */\n bg: string\n /** Foreground / primary text color → CSS variable --fg */\n fg: string\n\n // -- Optional enrichment (each falls back to color-mix from bg+fg) --\n\n /** Edge/connector color → CSS variable --line */\n line?: string\n /** Arrow heads, highlights, special nodes → CSS variable --accent */\n accent?: string\n /** Secondary text, edge labels → CSS variable --muted */\n muted?: string\n /** Node/box fill tint → CSS variable --surface */\n surface?: string\n /** Node/group stroke color → CSS variable --border */\n border?: string\n}\n\n// ============================================================================\n// Defaults\n// ============================================================================\n\n/** Default bg/fg when no colors are provided (zinc light) */\nexport const DEFAULTS: Readonly<{ bg: string; fg: string }> = {\n bg: '#FFFFFF',\n fg: '#27272A',\n} as const\n\n// ============================================================================\n// color-mix() weights for derived CSS variables\n//\n// When an optional enrichment variable is NOT set, we compute the derived\n// value by mixing --fg into --bg at these percentages. This produces a\n// coherent mono hierarchy on any bg/fg combination.\n// ============================================================================\n\nexport const MIX = {\n /** Primary text: near-full fg */\n text: 100, // just use --fg directly\n /** Secondary text (group headers): fg mixed at 60% */\n textSec: 60,\n /** Muted text (edge labels, notes): fg mixed at 40% */\n textMuted: 40,\n /** Faint text (de-emphasized): fg mixed at 25% */\n textFaint: 25,\n /** Edge/connector lines: fg mixed at 50% for clear visibility */\n line: 50,\n /** Arrow head fill: fg mixed at 85% for clear visibility */\n arrow: 85,\n /** Node fill tint: fg mixed at 3% */\n nodeFill: 3,\n /** Node/group stroke: fg mixed at 20% */\n nodeStroke: 20,\n /** Group header band tint: fg mixed at 5% */\n groupHeader: 5,\n /** Inner divider strokes: fg mixed at 12% */\n innerStroke: 12,\n /** Key badge background opacity (ER diagrams) */\n keyBadge: 10,\n} as const\n\n// ============================================================================\n// Well-known theme palettes\n//\n// Curated bg/fg pairs (+ optional enrichment) for popular editor themes.\n// Users can also extract from Shiki theme objects via fromShikiTheme().\n// ============================================================================\n\nexport const THEMES: Record<string, DiagramColors> = {\n 'zinc-light': {\n bg: '#FFFFFF',\n fg: '#27272A',\n },\n 'zinc-dark': {\n bg: '#18181B',\n fg: '#FAFAFA',\n },\n 'tokyo-night': {\n bg: '#1a1b26',\n fg: '#a9b1d6',\n line: '#3d59a1',\n accent: '#7aa2f7',\n muted: '#565f89',\n },\n 'tokyo-night-storm': {\n bg: '#24283b',\n fg: '#a9b1d6',\n line: '#3d59a1',\n accent: '#7aa2f7',\n muted: '#565f89',\n },\n 'tokyo-night-light': {\n bg: '#d5d6db',\n fg: '#343b58',\n line: '#34548a',\n accent: '#34548a',\n muted: '#9699a3',\n },\n 'catppuccin-mocha': {\n bg: '#1e1e2e',\n fg: '#cdd6f4',\n line: '#585b70',\n accent: '#cba6f7',\n muted: '#6c7086',\n },\n 'catppuccin-latte': {\n bg: '#eff1f5',\n fg: '#4c4f69',\n line: '#9ca0b0',\n accent: '#8839ef',\n muted: '#9ca0b0',\n },\n nord: {\n bg: '#2e3440',\n fg: '#d8dee9',\n line: '#4c566a',\n accent: '#88c0d0',\n muted: '#616e88',\n },\n 'nord-light': {\n bg: '#eceff4',\n fg: '#2e3440',\n line: '#aab1c0',\n accent: '#5e81ac',\n muted: '#7b88a1',\n },\n dracula: {\n bg: '#282a36',\n fg: '#f8f8f2',\n line: '#6272a4',\n accent: '#bd93f9',\n muted: '#6272a4',\n },\n 'github-light': {\n bg: '#ffffff',\n fg: '#1f2328',\n line: '#d1d9e0',\n accent: '#0969da',\n muted: '#59636e',\n },\n 'github-dark': {\n bg: '#0d1117',\n fg: '#e6edf3',\n line: '#3d444d',\n accent: '#4493f8',\n muted: '#9198a1',\n },\n 'solarized-light': {\n bg: '#fdf6e3',\n fg: '#657b83',\n line: '#93a1a1',\n accent: '#268bd2',\n muted: '#93a1a1',\n },\n 'solarized-dark': {\n bg: '#002b36',\n fg: '#839496',\n line: '#586e75',\n accent: '#268bd2',\n muted: '#586e75',\n },\n 'one-dark': {\n bg: '#282c34',\n fg: '#abb2bf',\n line: '#4b5263',\n accent: '#c678dd',\n muted: '#5c6370',\n },\n} as const\n\nexport type ThemeName = keyof typeof THEMES\n\n// ============================================================================\n// Shiki theme extraction\n//\n// Extracts DiagramColors from a Shiki ThemeRegistrationResolved object.\n// This provides native compatibility with any VS Code / TextMate theme.\n// ============================================================================\n\n/**\n * Minimal subset of Shiki's ThemeRegistrationResolved that we need.\n * We don't import from shiki to avoid a hard dependency.\n */\ninterface ShikiThemeLike {\n type?: string\n colors?: Record<string, string>\n tokenColors?: Array<{\n scope?: string | string[]\n settings?: { foreground?: string }\n }>\n}\n\n/**\n * Extract diagram colors from a Shiki theme object.\n * Works with any VS Code / TextMate theme loaded by Shiki.\n *\n * Maps editor UI colors to diagram roles:\n * editor.background → bg\n * editor.foreground → fg\n * editorLineNumber.fg → line (optional)\n * focusBorder / keyword → accent (optional)\n * comment token → muted (optional)\n * editor.selectionBackground→ surface (optional)\n * editorWidget.border → border (optional)\n *\n * @example\n * ```ts\n * import { getSingletonHighlighter } from 'shiki'\n * import { fromShikiTheme } from 'zombie-mermaid'\n *\n * const hl = await getSingletonHighlighter({ themes: ['tokyo-night'] })\n * const colors = fromShikiTheme(hl.getTheme('tokyo-night'))\n * const svg = renderMermaidSVG(code, colors)\n * ```\n */\nexport function fromShikiTheme(theme: ShikiThemeLike): DiagramColors {\n const c = theme.colors ?? {}\n const dark = theme.type === 'dark'\n\n // Helper: find a token color by scope name\n const tokenColor = (scope: string): string | undefined =>\n theme.tokenColors?.find((t) =>\n Array.isArray(t.scope) ? t.scope.includes(scope) : t.scope === scope,\n )?.settings?.foreground\n\n return {\n bg: c['editor.background'] ?? (dark ? '#1e1e1e' : '#ffffff'),\n fg: c['editor.foreground'] ?? (dark ? '#d4d4d4' : '#333333'),\n line: c['editorLineNumber.foreground'] ?? undefined,\n accent: c['focusBorder'] ?? tokenColor('keyword') ?? undefined,\n muted:\n tokenColor('comment') ?? c['editorLineNumber.foreground'] ?? undefined,\n surface: c['editor.selectionBackground'] ?? undefined,\n border: c['editorWidget.border'] ?? undefined,\n }\n}\n\n// ============================================================================\n// SVG style block — the CSS variable derivation system\n//\n// Generates the <style> content that maps user-facing variables (--bg, --fg,\n// --line, etc.) to internal derived variables (--_text, --_line, etc.) using\n// color-mix() fallbacks.\n// ============================================================================\n\n/**\n * Characters that must never appear in a `font` value once it's embedded in\n * the generated `<style>` block — whether it's treated as a CSS `var()`\n * reference or as a literal font name. Angle brackets would let the value\n * break out of the `<style>...</style>` element (e.g. a literal `</style>`\n * or an injected `<script>`); semicolons/braces would let it terminate the\n * current CSS declaration/rule and inject new ones.\n */\nconst UNSAFE_FONT_CHARS_RE = /[<>{};]/\n\n/**\n * Detect a syntactically well-formed CSS `var()` reference, e.g.\n * `var(--font-family-body)` or `var(--font, 'Fallback Font')` (a `var()`\n * with a quoted fallback argument). Requires balanced parens and rejects\n * any of `UNSAFE_FONT_CHARS_RE`.\n *\n * `font.startsWith('var(')` alone isn't enough: a caller could pass\n * something that merely starts with `var(` but isn't actually one (e.g.\n * `var(--x); } .evil{...}`). Anything that fails this stricter check is\n * treated as an untrusted/malformed value and falls back to being quoted\n * as a literal font name below — which neutralizes it, since a quoted\n * string isn't parsed as a `var()` call by CSS.\n */\nfunction isSafeCssVarReference(font: string): boolean {\n const trimmed = font.trim()\n if (!trimmed.startsWith('var(') || !trimmed.endsWith(')')) return false\n if (UNSAFE_FONT_CHARS_RE.test(trimmed)) return false\n let depth = 0\n for (const ch of trimmed) {\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth < 0) return false\n }\n }\n return depth === 0\n}\n\n/**\n * CSS generic font-family keywords. These are resolved by the browser/user\n * agent to a locally available font and are never real font names a Google\n * Fonts `@import` could fetch.\n */\nconst GENERIC_FONT_FAMILIES = new Set([\n 'sans-serif',\n 'serif',\n 'monospace',\n 'system-ui',\n 'ui-sans-serif',\n 'ui-serif',\n 'ui-monospace',\n 'ui-rounded',\n 'cursive',\n 'fantasy',\n 'math',\n 'emoji',\n 'fangsong',\n])\n\n/**\n * Detect a `font` value that names a font *stack* (comma-separated list,\n * e.g. `\"ui-sans-serif, system-ui, sans-serif\"`) or a single CSS generic\n * family keyword (e.g. `\"system-ui\"`) rather than a single concrete font\n * name. Neither is something a Google Fonts `@import` could ever\n * successfully fetch: a stack has no single `family=` value to request, and\n * a generic keyword is resolved locally by the browser, not hosted by\n * Google Fonts. Skipping the `@import` for these avoids baking a dead,\n * always-404 request into the SVG (see #223).\n */\nfunction isFontStackOrGenericFamily(font: string): boolean {\n const trimmed = font.trim()\n if (trimmed.includes(',')) return true\n return GENERIC_FONT_FAMILIES.has(trimmed.toLowerCase())\n}\n\n/**\n * The opening `<style>` tag every renderer uses — with a `nonce` attribute\n * when the caller supplied one (see `RenderOptions.nonce`, #216).\n *\n * All `<style>` emission points go through this one function so a nonce\n * can't be missed on one of them: under a nonce-based CSP a single\n * un-nonced `<style>` is silently dropped by the browser, which for the\n * theme block means an unstyled diagram with no console hint as to why.\n *\n * An empty/whitespace-only nonce is treated as unset — `nonce=\"\"` would\n * authorise nothing and only mislead a reader into thinking it did.\n * The value is attribute-escaped; a real nonce is base64 and never needs\n * escaping, but the option is a plain string and this keeps a stray `\"`\n * from breaking out of the attribute.\n */\nexport function styleOpenTag(nonce?: string): string {\n if (nonce === undefined || nonce.trim() === '') return '<style>'\n return `<style nonce=\"${escapeAttr(nonce)}\">`\n}\n\n/**\n * Build the CSS variable derivation rules for the SVG <style> block.\n *\n * When an optional variable (--line, --accent, etc.) is set on the SVG or\n * a parent element, it's used directly. When unset, the fallback computes\n * a blended value from --fg and --bg using color-mix().\n *\n * `nonce`, when given, lands on the `<style>` element as a `nonce`\n * attribute — see `styleOpenTag()`.\n *\n * `font` is user-supplied input. When it's a CSS `var(...)` reference (e.g.\n * `var(--font-family-body)`, letting the SVG inherit a host page's\n * design-system font), the Google Fonts `@import` is skipped — it would\n * otherwise URL-encode the literal `var(...)` string into a `family=` query\n * param and produce a guaranteed no-op request — and the value is emitted\n * unquoted, since a quoted string isn't parsed as a `var()` call by CSS.\n *\n * The `@import` is likewise skipped when `font` is a font *stack* (a\n * comma-separated list, e.g. `\"ui-sans-serif, system-ui, sans-serif\"`) or a\n * single CSS generic family keyword (e.g. `\"system-ui\"`) — see\n * `isFontStackOrGenericFamily`. Neither names a single concrete font that\n * Google Fonts could host, so importing one always produces a dead,\n * always-404 request (#223). This is additive to the `var()` skip above,\n * and only affects the `@import` decision — the `text { font-family: ... }`\n * rule still renders the literal value exactly as it does today.\n *\n * Any other value is treated as a literal font name: sanitized and quoted\n * exactly as before.\n *\n * `hasMonoFont`'s `.mono` rule (class-diagram method signatures, ER-diagram\n * attribute types) never reaches out to Google Fonts at all — it used to\n * `@import` 'JetBrains Mono' from Google's CDN, a third-party network\n * dependency a published library's default SVG output had no business\n * making (#1061, filed as a follow-up from #1059's ASCII-side self-hosting\n * fix). It now embeds {@link SVG_MONO_FONT_FACE_CSS} — a small, subsetted\n * (Basic Latin + Latin-1 Supplement), self-hosted `@font-face` as a base64\n * `woff2` data URI, generated by scripts/build-mono-font-subset.ts into\n * ./generated/mono-font-subset.ts — directly in the SVG's own `<style>`\n * block. Unlike `font` above, this isn't caller-configurable: it's the same\n * embed for every consumer, on by default, with no network request either\n * way — a strict improvement over the dead-or-alive CDN fetch it replaces,\n * and the only way a *standalone* SVG (no surrounding host page providing\n * its own copy of the font) renders `.mono` text correctly out of the box.\n */\nexport function buildStyleBlock(\n font: string,\n hasMonoFont: boolean,\n nonce?: string,\n): string {\n const isVarReference = isSafeCssVarReference(font)\n // Literal font names are sanitized before quoting (strip characters that\n // could break out of the CSS string or the <style> block). A validated\n // var() reference is passed through as-is so `var(--x, 'Fallback')` keeps\n // its own internal quotes intact.\n const safeFont = isVarReference ? font.trim() : font.replace(/[<>{};'\"]/g, '')\n const skipImport = isVarReference || isFontStackOrGenericFamily(font)\n\n const fontImports = skipImport\n ? []\n : [\n `@import url('https://fonts.googleapis.com/css2?family=${encodeURIComponent(safeFont)}:wght@400;500;600;700&display=swap');`,\n ]\n\n // Derived CSS variables: use override if set, else mix from bg+fg.\n // The --_ prefix signals \"private/derived\" — not meant for external override.\n const derivedVars = `\n /* Derived from --bg and --fg (overridable via --line, --accent, etc.) */\n --_text: var(--fg);\n --_text-sec: var(--muted, color-mix(in srgb, var(--fg) ${MIX.textSec}%, var(--bg)));\n --_text-muted: var(--muted, color-mix(in srgb, var(--fg) ${MIX.textMuted}%, var(--bg)));\n --_text-faint: color-mix(in srgb, var(--fg) ${MIX.textFaint}%, var(--bg));\n --_line: var(--line, color-mix(in srgb, var(--fg) ${MIX.line}%, var(--bg)));\n --_arrow: var(--accent, color-mix(in srgb, var(--fg) ${MIX.arrow}%, var(--bg)));\n --_node-fill: var(--surface, color-mix(in srgb, var(--fg) ${MIX.nodeFill}%, var(--bg)));\n --_node-stroke: var(--border, color-mix(in srgb, var(--fg) ${MIX.nodeStroke}%, var(--bg)));\n --_group-fill: var(--bg);\n --_group-hdr: color-mix(in srgb, var(--fg) ${MIX.groupHeader}%, var(--bg));\n --_inner-stroke: color-mix(in srgb, var(--fg) ${MIX.innerStroke}%, var(--bg));\n --_key-badge: color-mix(in srgb, var(--fg) ${MIX.keyBadge}%, var(--bg));`\n\n return [\n styleOpenTag(nonce),\n ...(fontImports.length > 0 ? [` ${fontImports.join('\\n ')}`] : []),\n // Self-hosted, embedded @font-face for `.mono` text — see the doc\n // comment above for why this never goes through the CDN-import/skip\n // logic `font` above does.\n ...(hasMonoFont ? [` ${SVG_MONO_FONT_FACE_CSS}`] : []),\n ` text { font-family: ${isVarReference ? safeFont : `'${safeFont}'`}, system-ui, sans-serif; }`,\n ...(hasMonoFont\n ? [\n ` .mono { font-family: '${SVG_MONO_FONT_FAMILY}', 'SF Mono', 'Fira Code', ui-monospace, monospace; }`,\n ]\n : []),\n ` svg {${derivedVars}`,\n ` }`,\n '</style>',\n ].join('\\n')\n}\n\n// ============================================================================\n// Fill → text color contrast\n//\n// When a node has a custom `fill` (from classDef/style) but no explicit\n// `color`, defaulting text to the ambient theme foreground can be unreadable\n// (e.g. white text on a light pastel fill in dark mode). If the fill is a\n// concrete, resolvable color (hex), compute a readable black/white text\n// color from its perceptual luminance. Anything else (CSS variable\n// references, named colors, malformed values) is left alone — the caller\n// falls back to the theme foreground.\n// ============================================================================\n\n/**\n * Perceptual luminance of an sRGB color on a 0-1 scale (0 = black, 1 =\n * white), using the ITU-R BT.601 luma weights (0.299/0.587/0.114).\n *\n * This is the simple, non-gamma-corrected \"perceived brightness\" formula\n * (as opposed to the gamma-corrected WCAG relative-luminance formula used\n * for contrast-ratio math). It's the right tool here: we just need a\n * black-vs-white pick, and BT.601 luma crosses black/white legibility at\n * ~0.5 on its own 0-1 scale, so the threshold in getReadableTextColor()\n * lines up directly with the formula instead of needing a separately\n * derived crossover constant. (True WCAG relative luminance would need a\n * ~0.18 threshold, not 0.5, to give the same black/white picks — see\n * https://www.w3.org/TR/WCAG20/#relativeluminancedef.)\n */\nfunction perceptualLuminance(r: number, g: number, b: number): number {\n return (0.299 * r + 0.587 * g + 0.114 * b) / 255\n}\n\n/**\n * Pick a readable text color for a given fill color.\n *\n * If `fill` is a concrete, resolvable hex color, returns pure black or\n * white depending on the fill's perceptual luminance (threshold 0.5: light\n * fills get dark text, dark fills get light text). If `fill` is undefined\n * or isn't a resolvable color (a `var(...)` reference, a named CSS color,\n * or malformed input), returns `fallback` unchanged so callers don't guess\n * at colors they can't actually evaluate.\n */\nexport function getReadableTextColor(\n fill: string | undefined,\n fallback: string,\n): string {\n if (!fill) return fallback\n const rgb = parseHexColor(fill)\n if (!rgb) return fallback\n const luminance = perceptualLuminance(...rgb)\n return luminance > 0.5 ? '#000000' : '#FFFFFF'\n}\n\n/**\n * Page-unique suffix generator for the root accessible-name `<title>` id.\n *\n * Mirrors `markerSuffix()` in packages/svg-renderer/src/renderer.ts, which solves the same\n * \"multiple SVGs inlined into one HTML page share a single id namespace\"\n * problem for arrow-marker ids — a marker's id is sanitized from its stroke\n * color, since two markers with the same color are meant to share one\n * definition. A title has no such natural per-call input (two diagrams can\n * easily have the same title text and must still get distinct ids), so this\n * increments a module-level counter instead: every `svgOpenTag()` call that\n * renders a title gets a fresh, never-repeated id for the lifetime of the\n * process, which is exactly what's needed for `aria-labelledby` to resolve\n * correctly when several rendered diagrams are concatenated into one page.\n */\nlet titleIdCounter = 0\n\nfunction nextTitleId(): string {\n titleIdCounter += 1\n return `zm-title-${titleIdCounter}`\n}\n\n/**\n * Test-only: reset the title-id counter so id assertions in tests don't\n * depend on how many titled diagrams earlier tests in the same file rendered.\n * Not part of the public API (not re-exported from src/index.ts).\n */\nexport function __resetSvgTitleIdCounterForTests(): void {\n titleIdCounter = 0\n}\n\n/**\n * The exact CSS declaration list the root `<svg style=\"…\">` attribute\n * carries: the theme custom properties (`--bg`, `--fg`, plus whichever\n * enrichment variables were actually provided — unset ones fall back to the\n * color-mix() derivations in the `<style>` block) and, unless `transparent`,\n * a `background: var(--bg)`.\n *\n * Exposed (via `themeCssVariables()` in src/index.ts) so a host that turns\n * the attribute off with `styleAttribute: false` — because its\n * Content-Security-Policy forbids `style=` attributes, which a nonce can\n * never authorise (#216) — can put the very same declarations in its own\n * stylesheet on a wrapper instead. Single source of truth for the variable\n * list: `svgOpenTag()` and the helper both call this, so the two can't\n * drift.\n *\n * Emitted compactly (`--bg:#fff;--fg:#000;background:var(--bg)`) rather\n * than pretty-printed: it's the same string the attribute has always\n * carried, so default output stays byte-identical. Colour values are the\n * caller's own `RenderOptions` and are not escaped here — same as before.\n */\nexport function themeStyleDeclarations(\n colors: DiagramColors,\n transparent?: boolean,\n): string {\n const vars = [\n `--bg:${colors.bg}`,\n `--fg:${colors.fg}`,\n colors.line ? `--line:${colors.line}` : '',\n colors.accent ? `--accent:${colors.accent}` : '',\n colors.muted ? `--muted:${colors.muted}` : '',\n colors.surface ? `--surface:${colors.surface}` : '',\n colors.border ? `--border:${colors.border}` : '',\n ]\n .filter(Boolean)\n .join(';')\n\n const bgStyle = transparent ? '' : ';background:var(--bg)'\n return `${vars}${bgStyle}`\n}\n\n/**\n * Build the SVG opening tag with CSS variables set as inline styles.\n * Only includes optional variables that are actually provided — unset ones\n * will fall back to the color-mix() derivations in the <style> block.\n *\n * `styleAttribute: false` drops the `style=\"…\"` attribute entirely (the\n * host supplies those declarations from its own stylesheet — see\n * `themeStyleDeclarations()` and `RenderOptions.styleAttribute`, #216).\n * Every other attribute — `xmlns`, `viewBox`, `width`/`height`, the\n * `role`/`aria-*` accessibility attributes, and anything a caller splices\n * on afterwards (`data-src`, `data-xychart-colors`) — is unaffected.\n *\n * Also handles the SVG's accessible name (see `RenderOptions.title` /\n * `RenderOptions.decorative` in packages/core/src/types.ts, and GitHub issue #215):\n *\n * - `decorative: true` → `aria-hidden=\"true\"` on the root, no `role` or\n * name. Use for a diagram already described in surrounding prose; `title`\n * is ignored when this is set.\n * - `title` given (and not decorative) → `role=\"img\"` +\n * `aria-labelledby=\"zm-title-N\"` on the root, plus a `<title id=\"zm-title-N\">`\n * as the SVG's first child holding the escaped text. This is the standard\n * SVG/WAI-ARIA \"accessible name via a referenced title element\" technique\n * (see MDN \"SVG accessibility\" and the WAI-ARIA `img` role: an element\n * with `role=\"img\"` needs a computed accessible name to be meaningful to\n * assistive tech; `aria-labelledby` pointing at a `<title>` is the\n * documented way to supply one for inline SVG).\n * - Neither given → `role=\"img\"` only, no name. This still stops assistive\n * tech from treating the SVG as a plain group (which announces every\n * node/edge label individually, out of reading order — the core bug in\n * #215), without fabricating a name the library can't honestly claim. It\n * reads the same as an `<img>` with no `alt`: present as a single image,\n * unlabeled.\n *\n * `hasInteractiveLinks` overrides all of the above (see #239): a `click A\n * \"url\"` statement renders a real, focusable `<a href>` inside the SVG, and\n * both `role=\"img\"` and `aria-hidden=\"true\"` are unsafe on an ancestor of a\n * focusable element — `role=\"img\"` tells assistive tech to stop descending\n * into children (the link becomes unreachable by screen reader while still\n * Tab-focusable), and `aria-hidden=\"true\"` on a focusable descendant is an\n * explicit WAI-ARIA violation (the link vanishes from the accessibility tree\n * but not from the tab order). When any node has a link, the root gets no\n * `role` at all — `decorative` is silently overridden rather than honored,\n * since aria-hiding a diagram that contains an actionable link is not a safe\n * request to fulfill — but `title`/`aria-labelledby` still apply if given:\n * an accessible name is still valid on an element with no explicit role.\n *\n * The per-node/per-point `<title>` tooltips added by earlier work (nested\n * inside `<g>` elements, with no `id` attribute) are unaffected: they never\n * collide with the root title's generated id, and a root `<title>` alongside\n * unrelated descendant `<title>` elements is valid SVG.\n *\n * @param transparent - If true, omits the background style for transparent SVGs\n * @param title - Accessible name text. Ignored when `decorative` is true\n * and there are no interactive links (see `hasInteractiveLinks`).\n * @param decorative - Marks the SVG as decorative (`aria-hidden=\"true\"`).\n * Overridden when `hasInteractiveLinks` is true.\n * @param hasInteractiveLinks - True if any node has a `click`-based `href`.\n * See #239 — forces no root `role` so the link\n * stays reachable, regardless of `decorative`.\n * @param styleAttribute - Emit the root `style=\"…\"` attribute. Default true.\n */\nexport function svgOpenTag(\n width: number,\n height: number,\n colors: DiagramColors,\n transparent?: boolean,\n title?: string,\n decorative?: boolean,\n hasInteractiveLinks?: boolean,\n styleAttribute: boolean = true,\n): string {\n const styleAttr = styleAttribute\n ? ` style=\"${themeStyleDeclarations(colors, transparent)}\"`\n : ''\n\n let a11yAttrs = ''\n let titleEl = ''\n if (hasInteractiveLinks) {\n // No root role: role=\"img\" or aria-hidden would hide a real, focusable\n // <a href> descendant from assistive tech while leaving it Tab-reachable.\n if (title) {\n const id = nextTitleId()\n a11yAttrs = ` aria-labelledby=\"${id}\"`\n titleEl = `\\n <title id=\"${id}\">${escapeXml(title)}</title>`\n }\n } else if (decorative) {\n a11yAttrs = ' aria-hidden=\"true\"'\n } else {\n a11yAttrs = ' role=\"img\"'\n if (title) {\n const id = nextTitleId()\n a11yAttrs += ` aria-labelledby=\"${id}\"`\n titleEl = `\\n <title id=\"${id}\">${escapeXml(title)}</title>`\n }\n }\n\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${width} ${height}\" ` +\n `width=\"${width}\" height=\"${height}\"${a11yAttrs}${styleAttr}>` +\n titleEl\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/click-directive.ts","../src/color-utils.ts","../src/statements.ts","../src/diagram-type.ts","../src/direction.ts","../src/direction-override.ts","../src/init-directive.ts","../src/text-metrics.ts","../src/render-utils.ts","../src/multiline-utils.ts","../src/style-directives.ts","../src/generated/mono-font-subset.ts","../src/theme.ts"],"sourcesContent":["import type { NodeInteraction } from './types.ts'\n\n// ============================================================================\n// Shared `click` directive parsing and href sanitization.\n//\n// Every diagram type that recognizes Mermaid's `click` statement (flowchart/\n// state via src/parser.ts, class diagrams via\n// packages/mermaid-parser/src/class/parser.ts) shares this exact grammar\n// and the same href-safety rules — pulled out here so\n// neither copy can drift from the other. See\n// docs/decisions/no-script-interactivity.md for the tier model this\n// implements: an `href` becomes a real `<a>` link (tier 2), a tooltip\n// becomes a `<title>` (tier 1), and a `call`/`callback` binding is recorded\n// as data but never executed (tier 3 stays unimplemented, permanently).\n// ============================================================================\n\n/**\n * Parse a `click <id> ...` statement and record the resulting\n * {@link NodeInteraction} in `interactions`, keyed by whatever id scheme the\n * caller's diagram type uses (flowchart/state node id, class diagram class\n * id, ...).\n *\n * Supported forms:\n * click id \"url\" [\"tooltip\"] [_blank|_self|_parent|_top]\n * click id href \"url\" [\"tooltip\"] [_blank|_self|_parent|_top]\n * click id call fn() [\"tooltip\"]\n * click id callback fn() [\"tooltip\"]\n *\n * A line that doesn't match `click <id> ...` at all is a no-op — callers are\n * expected to have already gated on a leading `click` keyword before calling\n * this (see the `/^click\\s+/i` checks at each call site) purely so this\n * function's own regex can stay anchored to the id capture.\n */\nexport function applyClickStatement(\n line: string,\n interactions: Map<string, NodeInteraction>,\n): void {\n const match = line.match(/^click\\s+([\\w\\p{L}-]+)\\s+(.*)$/iu)\n if (!match) return\n\n const nodeId = match[1]!\n let rest = match[2]!.trim()\n\n const interaction: NodeInteraction = { ...interactions.get(nodeId) }\n\n // `call fn()` / `callback fn()` — a script binding.\n const callMatch = rest.match(/^(?:call|callback)\\s+(.+?)\\s*$/i)\n if (callMatch) {\n // A trailing quoted tooltip may follow the callback expression.\n const withTooltip = callMatch[1]!.match(/^(.*?\\))\\s+\"([^\"]*)\"\\s*$/)\n if (withTooltip) {\n interaction.callback = withTooltip[1]!.trim()\n interaction.tooltip = withTooltip[2]\n } else {\n interaction.callback = callMatch[1]!.trim()\n }\n interactions.set(nodeId, interaction)\n return\n }\n\n // Optional explicit `href` keyword.\n rest = rest.replace(/^href\\s+/i, '')\n\n // Remaining tokens: \"url\" [\"tooltip\"] [_target]\n const quoted = [...rest.matchAll(/\"([^\"]*)\"/g)].map((m) => m[1]!)\n if (quoted.length > 0) interaction.href = quoted[0]\n if (quoted.length > 1) interaction.tooltip = quoted[1]\n\n const targetMatch = rest.match(/(_blank|_self|_parent|_top)\\s*$/i)\n if (targetMatch) interaction.target = targetMatch[1]!.toLowerCase()\n\n if (interaction.href !== undefined || interaction.tooltip !== undefined) {\n interactions.set(nodeId, interaction)\n }\n}\n\n/**\n * Accept only link schemes that cannot execute script.\n *\n * A `click` target comes from diagram text, which may be untrusted. An\n * `href` of `javascript:...` (or a `data:` URL containing markup) would turn\n * a rendered diagram into an XSS vector for any page that inlines the SVG, so\n * anything but http/https/mailto and same-document or relative references is\n * dropped rather than emitted.\n *\n * C0 controls are rejected outright, before any other check. The URL parser\n * strips tab and newline from *anywhere* in a URL, so `java\\tscript:` reaches\n * a browser as `javascript:` — while the scheme match below sees `java\\t…`,\n * finds no scheme, and waves it through as a relative reference. Splitting a\n * blocked scheme with a control character is the whole bypass; there is no\n * legitimate URL with a raw control in it, so the entire range goes.\n */\nexport function safeHref(href: string | undefined): string | undefined {\n if (!href) return undefined\n\n if (/[\\x00-\\x1F\\x7F]/.test(href)) return undefined\n\n const trimmed = href.trim()\n // Relative, absolute-path, and fragment references carry no scheme.\n if (/^[./#?]/.test(trimmed)) return trimmed\n\n const scheme = trimmed.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/)?.[1]\n if (scheme === undefined) return trimmed\n\n return /^(https?|mailto)$/i.test(scheme) ? trimmed : undefined\n}\n","// ============================================================================\n// CSS color primitives — parse, mix, and format sRGB colors.\n//\n// Shared by the SVG theme system (packages/core/src/theme.ts), the ASCII renderer's\n// DiagramColors → AsciiTheme bridge (src/ascii/ansi.ts), and the\n// `resolveColors` post-processor (packages/svg-renderer/src/resolve-colors.ts). All three need\n// the same answer to \"what does `color-mix(in srgb, fg 50%, bg)` come out\n// to?\", so the arithmetic lives here exactly once — the MIX percentages in\n// packages/core/src/theme.ts stay the single source of truth for *which* percentages the\n// renderer uses, and this module is the single source of truth for what a\n// percentage *means*.\n// ============================================================================\n\n/** An sRGB color: 0-255 channels plus a 0-1 alpha. */\nexport interface RgbaColor {\n r: number\n g: number\n b: number\n a: number\n}\n\n/** Matches #rgb, #rgba, #rrggbb, #rrggbbaa hex color literals. */\nconst HEX_COLOR_RE =\n /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/\n\n/**\n * Matches `rgb()` / `rgba()` in both the legacy comma syntax\n * (`rgb(1, 2, 3)`, `rgba(1, 2, 3, 0.5)`) and the modern space syntax\n * (`rgb(1 2 3 / 50%)`). Channels may be integers or percentages; alpha may\n * be a 0-1 number or a percentage.\n */\nconst RGB_FUNC_RE =\n /^rgba?\\(\\s*([\\d.]+%?)\\s*[, ]\\s*([\\d.]+%?)\\s*[, ]\\s*([\\d.]+%?)\\s*(?:[,/]\\s*([\\d.]+%?)\\s*)?\\)$/i\n\n/**\n * Parse a hex color string into 0-255 RGB components, ignoring any alpha\n * digits. Returns null for anything that isn't a well-formed hex literal\n * (e.g. `var(--foo)`, named CSS colors, or attribute-injection payloads) —\n * the caller should treat those as unresolvable and fall back.\n */\nexport function parseHexColor(value: string): [number, number, number] | null {\n const rgba = parseHexRgba(value)\n return rgba ? [rgba.r, rgba.g, rgba.b] : null\n}\n\n/** Parse a hex color literal (3/4/6/8 digits) into an RgbaColor, or null. */\nexport function parseHexRgba(value: string): RgbaColor | null {\n const match = HEX_COLOR_RE.exec(value.trim())\n if (!match) return null\n // HEX_COLOR_RE has exactly one, non-optional capture group, so it's always\n // populated when `match` is non-null — but that's a fact about the regex's\n // construction, not something the type checker can see. Guard explicitly\n // (falling back to \"unresolvable\", same as a non-match) rather than\n // asserting past it, so a future edit to the pattern can't silently turn\n // this into a runtime `undefined`.\n const captured = match[1]\n if (captured === undefined) return null\n let hex = captured\n if (hex.length === 3 || hex.length === 4) {\n hex = hex\n .split('')\n .map((c) => c + c)\n .join('')\n }\n return {\n r: parseInt(hex.slice(0, 2), 16),\n g: parseInt(hex.slice(2, 4), 16),\n b: parseInt(hex.slice(4, 6), 16),\n a: hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1,\n }\n}\n\n/** Parse one rgb()/rgba() channel token: an integer/decimal or a percentage of 255. */\nfunction parseRgbChannel(token: string): number {\n return token.endsWith('%')\n ? (Number(token.slice(0, -1)) / 100) * 255\n : Number(token)\n}\n\n/** Parse an alpha token: a 0-1 number or a percentage. */\nfunction parseAlpha(token: string | undefined): number {\n if (token === undefined) return 1\n return token.endsWith('%') ? Number(token.slice(0, -1)) / 100 : Number(token)\n}\n\n/**\n * Parse a concrete CSS color value into sRGB components.\n *\n * Accepts the forms this library's own theme system produces or documents:\n * hex literals (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`), `rgb()`/`rgba()`,\n * and the `transparent` keyword. Anything else — CSS named colors, `hsl()`,\n * `var(...)`, an unresolved `color-mix(...)` — returns null so the caller\n * can leave it untouched rather than guess.\n */\nexport function parseCssColor(value: string): RgbaColor | null {\n const trimmed = value.trim()\n if (trimmed.toLowerCase() === 'transparent') {\n return { r: 0, g: 0, b: 0, a: 0 }\n }\n const hex = parseHexRgba(trimmed)\n if (hex) return hex\n const rgb = RGB_FUNC_RE.exec(trimmed)\n if (rgb) {\n const [, rTok, gTok, bTok, aTok] = rgb\n if (rTok === undefined || gTok === undefined || bTok === undefined) {\n return null\n }\n const color = {\n r: parseRgbChannel(rTok),\n g: parseRgbChannel(gTok),\n b: parseRgbChannel(bTok),\n a: parseAlpha(aTok),\n }\n if ([color.r, color.g, color.b, color.a].some((n) => Number.isNaN(n))) {\n return null\n }\n return color\n }\n return null\n}\n\n/**\n * Evaluate `color-mix(in srgb, c1 p1%, c2 p2%)` per CSS Color Module\n * Level 5 (https://www.w3.org/TR/css-color-5/#color-mix): percentages are\n * normalized so they sum to 100 (a sum under 100 additionally scales the\n * result's alpha by `sum / 100`), and the two colors are interpolated in\n * premultiplied-alpha space, so mixing an opaque color toward\n * `transparent` fades its alpha without darkening it.\n *\n * `p1`/`p2` follow the spec's omission rules: pass `undefined` for an\n * omitted percentage (both omitted → 50/50; one omitted → 100 minus the\n * other). Returns null when both percentages are zero, which the spec\n * defines as invalid.\n */\nexport function mixSrgb(\n c1: RgbaColor,\n c2: RgbaColor,\n p1?: number,\n p2?: number,\n): RgbaColor | null {\n let w1: number\n let w2: number\n if (p1 === undefined && p2 === undefined) {\n w1 = 50\n w2 = 50\n } else if (p1 === undefined) {\n w2 = p2 ?? 50\n w1 = 100 - w2\n } else if (p2 === undefined) {\n w1 = p1\n w2 = 100 - w1\n } else {\n w1 = p1\n w2 = p2\n }\n const sum = w1 + w2\n if (sum === 0) return null\n const alphaMultiplier = sum < 100 ? sum / 100 : 1\n w1 /= sum\n w2 /= sum\n\n const alpha = c1.a * w1 + c2.a * w2\n const premul = (ch1: number, ch2: number): number =>\n alpha === 0 ? 0 : (ch1 * c1.a * w1 + ch2 * c2.a * w2) / alpha\n\n return {\n r: premul(c1.r, c2.r),\n g: premul(c1.g, c2.g),\n b: premul(c1.b, c2.b),\n a: alpha * alphaMultiplier,\n }\n}\n\n/**\n * Mix `fg` into `bg` at `pct` percent — the shape every MIX entry in\n * packages/core/src/theme.ts uses (`color-mix(in srgb, var(--fg) N%, var(--bg))`).\n * Returns `fg` unchanged when either input isn't a concrete color this\n * module can parse (a `var(...)` reference, a named color).\n */\nexport function mixHexColors(fg: string, bg: string, pct: number): string {\n const f = parseCssColor(fg)\n const b = parseCssColor(bg)\n if (!f || !b) return fg\n const mixed = mixSrgb(f, b, pct, 100 - pct)\n return mixed ? formatCssColor(mixed) : fg\n}\n\nconst clampByte = (n: number): number =>\n Math.min(255, Math.max(0, Math.round(n)))\n\n/**\n * Serialize a color as `#rrggbb` when fully opaque, otherwise as\n * `rgba(r, g, b, a)` — the two forms every SVG consumer (browsers, resvg,\n * librsvg, Inkscape) agrees on.\n */\nexport function formatCssColor(c: RgbaColor): string {\n const r = clampByte(c.r)\n const g = clampByte(c.g)\n const b = clampByte(c.b)\n const a = Math.min(1, Math.max(0, c.a))\n if (a >= 1) {\n return '#' + [r, g, b].map((n) => n.toString(16).padStart(2, '0')).join('')\n }\n return `rgba(${r}, ${g}, ${b}, ${Number(a.toFixed(3))})`\n}\n","// ============================================================================\n// zombie-mermaid — shared statement splitting\n//\n// Every parser entry point (flowchart/state in src/parser.ts, the four\n// specialized parsers dispatched from src/index.ts and src/ascii/index.ts)\n// needs the same thing: turn raw Mermaid source into a list of trimmed,\n// comment-free statements. They each used to do it inline with\n// `.split('\\n').map(trim).filter(...)`, which meant semicolon separators —\n// valid Mermaid, and the form `detectDiagramType` already assumed when\n// isolating the header — were understood by nobody.\n//\n// This module is the single source of truth for \"what counts as one\n// statement\".\n// ============================================================================\n\n/**\n * Matches a character reference in either spelling Mermaid accepts.\n *\n * HTML/XML form: `&`, `#`, `😀`.\n * Mermaid's own form, which uses `#` where HTML uses `&`: `#59;`, `#9829;`,\n * `#quot;` — the documented way to put a literal semicolon, quote, or symbol\n * into a label.\n *\n * Both end in a semicolon that belongs to the reference, not to statement\n * separation. Splitting one apart corrupts the label *and* invents a\n * statement out of the remainder.\n */\nconst ENTITY_AT = /^[&#](?:#?[0-9]+|#?[xX][0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/\n\n/** Characters that can open a character reference. */\nconst ENTITY_STARTERS = new Set(['&', '#'])\n\n/**\n * Index of the `%%` that starts a comment on this line, or -1.\n *\n * A Mermaid comment runs to the end of the line, so everything after it —\n * including any further `;`-separated fragments — is commented out. Scanning\n * for it before splitting is what makes that true: dropping fragments that\n * merely *begin* with `%%` afterwards leaves `graph TD; A-->B; %% note; C-->D`\n * still parsing `C-->D` as code.\n *\n * Quote- and reference-aware for the same reasons the splitter is, so a `%%`\n * inside a label is text rather than a comment marker.\n */\nfunction commentStart(line: string): number {\n let quote: '\"' | \"'\" | null = null\n\n for (let i = 0; i < line.length; i++) {\n const ch = line[i]!\n\n if (quote !== null) {\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ENTITY_STARTERS.has(ch)) {\n const entity = ENTITY_AT.exec(line.slice(i))\n if (entity) {\n i += entity[0].length - 1\n continue\n }\n }\n if (ch === '%' && line[i + 1] === '%') return i\n }\n\n return -1\n}\n\n/**\n * Split one already-newline-separated line on its statement-separating\n * semicolons.\n *\n * A semicolon separates statements unless it is:\n * - inside a quoted string (`A[\"a; b\"]`), or\n * - the terminator of a character reference (`A[&]`).\n *\n * Quote tracking is deliberately simple — Mermaid has no escape sequence\n * inside quoted labels, so a quote character always opens or closes a span.\n */\nfunction splitOnSemicolons(line: string): string[] {\n const parts: string[] = []\n let current = ''\n let quote: '\"' | \"'\" | null = null\n\n for (let i = 0; i < line.length; i++) {\n const ch = line[i]!\n\n if (quote !== null) {\n current += ch\n if (ch === quote) quote = null\n continue\n }\n\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n current += ch\n continue\n }\n\n if (ENTITY_STARTERS.has(ch)) {\n // Copy a whole character reference in one go so its trailing ';' can't\n // be read as a separator. Guarded on '#' as well as '&': Mermaid's own\n // `#59;` spelling is far more common in diagrams than the HTML one.\n const entity = ENTITY_AT.exec(line.slice(i))\n if (entity) {\n current += entity[0]\n i += entity[0].length - 1\n continue\n }\n }\n\n if (ch === ';') {\n parts.push(current)\n current = ''\n continue\n }\n\n current += ch\n }\n\n parts.push(current)\n return parts\n}\n\n/**\n * One statement produced by the splitter, paired with the 1-based physical\n * source line it came from.\n *\n * `line` is captured while walking `text.split('\\n')`, before blank lines\n * and `%%` comment lines are dropped — so it survives being the original\n * source line number even though a statement's array index no longer does.\n * This is what lets every parser built on top of `splitStatements`/\n * `splitStatementsByLine` report *where* an error is, not just quote back\n * the offending statement's text (see issue #760).\n */\nexport interface Statement {\n text: string\n line: number\n}\n\n/**\n * Split Mermaid source into trimmed statements, grouped by the physical\n * source line each one came from — dropping blank lines and `%%` comment\n * lines.\n *\n * Newlines and semicolons both separate statements, matching Mermaid's own\n * `graph TD; A-->B;` form. Comments are removed *before* semicolon splitting\n * so that a `;` inside a comment can't resurrect the rest of that line as\n * code.\n *\n * The grouping is what lets a caller tell a genuine multi-line continuation\n * (crossing from one line's group into the next) apart from an explicit\n * `;`-separated statement on the *same* line (a later entry within one\n * group) — see `src/parser.ts`'s `mergeContinuationLines`, which only\n * treats the former as mergeable.\n */\nexport function splitStatementsByLine(text: string): Statement[][] {\n const groups: Statement[][] = []\n const rawLines = text.split('\\n')\n\n for (let lineIndex = 0; lineIndex < rawLines.length; lineIndex++) {\n let line = rawLines[lineIndex]!.trim()\n const lineNumber = lineIndex + 1\n\n /*\n * Cut the comment off first. A Mermaid comment runs to end of line, so\n * anything after `%%` is commented out — including further `;`-separated\n * fragments. Splitting first and discarding fragments that start with\n * `%%` would leave `A-->B; %% note; C-->D` parsing `C-->D` as code.\n */\n const comment = commentStart(line)\n if (comment !== -1) line = line.slice(0, comment).trim()\n if (line.length === 0) continue\n\n const statements: Statement[] = []\n for (const part of splitOnSemicolons(line)) {\n const statement = part.trim()\n if (statement.length === 0) continue\n statements.push({ text: statement, line: lineNumber })\n }\n if (statements.length > 0) groups.push(statements)\n }\n\n return groups\n}\n\n/**\n * Split Mermaid source into trimmed statements, dropping blank lines and\n * `%%` comment lines. See `splitStatementsByLine` for the line-grouped form\n * this flattens.\n */\nexport function splitStatements(text: string): Statement[] {\n return splitStatementsByLine(text).flat()\n}\n","// ============================================================================\n// zombie-mermaid — shared diagram-type detection\n//\n// Both the SVG renderer (src/index.ts) and the ASCII renderer\n// (src/ascii/index.ts) need to classify Mermaid source text before\n// dispatching to the right parse/layout/render pipeline. This module is the\n// single source of truth for that classification so the two backends can\n// never drift out of sync with each other again.\n// ============================================================================\n\nimport { splitStatements } from './statements.ts'\n\n/**\n * Every diagram type this library can detect and route to a renderer — the\n * runtime source of truth `DiagramType` is derived from, so consumers that\n * need to enumerate the types (e.g. the MCP `list_diagram_types` tool) never\n * keep a duplicate list that could drift.\n */\nexport const DIAGRAM_TYPES = [\n 'flowchart',\n 'sequence',\n 'class',\n 'er',\n 'xychart',\n 'architecture',\n\n 'c4',\n] as const\n\n/** The diagram types this library can detect and route to a renderer. */\nexport type DiagramType = (typeof DIAGRAM_TYPES)[number]\n\n/**\n * Detect the diagram type from the mermaid source text.\n * Returns the type keyword used for routing to the correct pipeline.\n *\n * The header keyword is the first *statement*, not the first line: Mermaid\n * allows `;` as a statement separator, so `flowchart TD;A-->B` has its header\n * and first edge on one line. Uses the same splitter every parser uses, so\n * routing and parsing can never disagree about where the header ends.\n */\nexport function detectDiagramType(text: string): DiagramType {\n const firstLine = splitStatements(text)[0]?.text.toLowerCase() ?? ''\n\n if (/^xychart(?:-beta)?(?:\\s|$)/.test(firstLine)) return 'xychart'\n if (/^architecture(?:-beta)?\\s*$/.test(firstLine)) return 'architecture'\n\n if (\n /^c4(?:context|container|component|dynamic|deployment)\\s*$/.test(firstLine)\n )\n return 'c4'\n if (/^sequencediagram\\s*$/.test(firstLine)) return 'sequence'\n if (/^classdiagram\\s*$/.test(firstLine)) return 'class'\n if (/^erdiagram\\s*$/.test(firstLine)) return 'er'\n\n // Default: flowchart/state (handled by parseMermaid internally)\n return 'flowchart'\n}\n","// ============================================================================\n// Direction token guard\n//\n// Lives in `core` rather than beside the flowchart parser that used to own\n// it (`src/parser.ts`) because `direction-override.ts` — also `core`, and\n// reached from the ASCII entry — needs it. Left in the parser, `core` would\n// import `mermaid-parser`, i.e. the shared package would depend on one of\n// its own consumers (zombie-mermaid#625, umbrella #620).\n//\n// `toDirection` joined `isDirection` here under #624 for the identical\n// reason: it used to live in `src/parser.ts` too, with that file as its\n// only caller, until splitting `er/parser.ts` out into\n// `packages/mermaid-parser/` gave it a second caller that cannot import the\n// umbrella's `src/parser.ts` without creating the same cycle `isDirection`\n// was moved to avoid. `src/parser.ts` now imports it from here instead of\n// defining it.\n//\n// `Direction` is only a compile-time guarantee, so every entry point that\n// accepts a direction from outside (`RenderOptions.direction`, the CLI's\n// `--direction` flag, a parsed `graph LR` header) re-checks it at runtime\n// through this guard.\n// ============================================================================\n\nimport type { Direction } from './types.ts'\n\n/** True when `value` is one of the five directions Mermaid recognizes. */\nexport function isDirection(value: string): value is Direction {\n return (\n value === 'TD' ||\n value === 'TB' ||\n value === 'LR' ||\n value === 'BT' ||\n value === 'RL'\n )\n}\n\n/**\n * Normalize a regex-captured direction token to a `Direction`.\n *\n * Accepts `string | undefined` because every call site passes a regex\n * capture group value (`match[1]`, typed as possibly-`undefined` under\n * `noUncheckedIndexedAccess`) straight through. Each call site's regex\n * guards the capture with the same `(TD|TB|LR|BT|RL)` alternation\n * (case-insensitively), so the group always participates in the match in\n * practice — but that's a regex-structure guarantee the type checker can't\n * see across the call site. Validating `undefined` here, instead of\n * asserting non-null at each call site, turns a hypothetical violation into\n * a clear, descriptive error rather than a raw `undefined` crash.\n */\nexport function toDirection(raw: string | undefined): Direction {\n const upper = raw?.toUpperCase()\n if (upper === undefined || !isDirection(upper)) {\n throw new Error(`Invalid direction: \"${raw}\"`)\n }\n return upper\n}\n","// ============================================================================\n// Render-time direction override (issue #276)\n//\n// `RenderOptions.direction` / `AsciiRenderOptions.direction` replace the\n// *top-level* direction a diagram declared in its source (a flowchart's\n// `graph LR` header, a state diagram's or ER diagram's `direction LR`\n// line) after parsing and before layout. Nothing here touches the source\n// text, and nothing here touches a nested subgraph's or composite state's\n// own `direction` — those live on the subgraph objects, not on the field\n// replaced below, so they keep winning locally over the top-level\n// direction exactly as they do relative to the diagram's own header.\n//\n// Shared by the SVG entry (src/index.ts) and the ASCII entry\n// (src/ascii/index.ts). Deliberately dependency-free beyond the\n// `isDirection` guard so the `zombie-mermaid/ascii` subpath stays free of\n// elkjs — the guard itself moved out of `src/parser.ts` and into this\n// package (see `direction.ts`) so that staying dependency-free survives\n// the workspace split (zombie-mermaid#625).\n// ============================================================================\n\nimport type { Direction } from './types.ts'\nimport { isDirection } from './direction.ts'\n\n/**\n * Return `diagram` with its top-level `direction` replaced by `override`,\n * or `diagram` itself (same reference) when there is nothing to apply.\n *\n * The parsed diagram is never mutated — a shallow clone carries the new\n * direction, so `parseMermaid()` output a caller holds on to stays exactly\n * what the parser produced, and a second render of the same parsed object\n * without the option sees the source direction again.\n *\n * `isDirection` re-checks the value at runtime because `Direction` is only\n * a compile-time guarantee: a plain-JavaScript caller can pass any string.\n * An unrecognized value is ignored (the diagram's own direction stands)\n * rather than thrown on — the same lenient treatment every other\n * `RenderOptions` value gets, e.g. an unknown `interactivity` or `curve`\n * string falls back to its default instead of failing the render.\n */\nexport function withDirectionOverride<T extends { direction?: Direction }>(\n diagram: T,\n override: Direction | undefined,\n): T {\n if (override === undefined || !isDirection(override)) return diagram\n return { ...diagram, direction: override }\n}\n","// ============================================================================\n// zombie-mermaid — %%{init: ...}%% configuration directives\n//\n// Mermaid lets a diagram carry its own configuration inline:\n//\n// %%{init: {\"theme\": \"dark\", \"flowchart\": {\"curve\": \"basis\"}}}%%\n// flowchart TD\n// A --> B\n//\n// The payload is JSON-ish: Mermaid accepts unquoted keys and single quotes,\n// which strict JSON.parse rejects, so it is normalized before parsing.\n//\n// A directive only ever *supplies defaults*. Explicit render options passed\n// by the caller win, because the caller is closer to the user's intent than\n// text embedded in a diagram — and because a diagram from an untrusted source\n// should not be able to override the host application's rendering choices.\n// ============================================================================\n\nimport type { RenderOptions } from './types.ts'\n\n/** How an edge path is interpolated between its routed points. */\nexport type CurveStyle =\n 'linear' | 'basis' | 'natural' | 'step' | 'stepBefore' | 'stepAfter'\n\nconst CURVE_STYLES = new Set<CurveStyle>([\n 'linear',\n 'basis',\n 'natural',\n 'step',\n 'stepBefore',\n 'stepAfter',\n])\n\n/** Configuration a diagram can set for itself via `%%{init: ...}%%`. */\nexport interface InitConfig {\n theme?: string\n /** Flowchart edge interpolation. */\n curve?: CurveStyle\n /** Recognized but not acted on — see `IGNORED_KEYS`. */\n ignored: string[]\n}\n\n/**\n * Directive keys that are parsed and deliberately not acted on, with the\n * reason. Reported on the result so a caller can surface them rather than\n * having the setting vanish silently.\n */\nconst IGNORED_KEYS: Record<string, string> = {\n theme:\n \"colors come from the caller's bg/fg render options, which are usually CSS variables so a diagram inherits the host page's light/dark. A diagram-supplied theme would hard-code colors that fight it. Mermaid's theme names (default/dark/forest/neutral) also have no equivalent in this renderer's palettes — pass `bg`/`fg`, or a THEMES entry, instead\",\n securitylevel:\n 'this renderer emits static SVG and never executes diagram-supplied script, so there is no sandbox to configure',\n defaultrenderer:\n 'ELK is the only layout engine; dagre/elk selection has no effect',\n fontfamily: 'use the `font` render option instead',\n htmllabels:\n 'labels are always rendered as SVG text; there is no HTML label mode',\n maxtextsize: 'no text-size limit is enforced',\n startonload: 'not a browser auto-render integration',\n}\n\n/**\n * Matches a `%%{init: ... }%%` or `%%{initialize: ... }%%` directive.\n *\n * The body is captured lazily up to the closing `}%%` so a directive sharing\n * a line with other content does not swallow it.\n */\nconst INIT_DIRECTIVE = /^\\s*%%\\{\\s*(?:init|initialize)\\s*:\\s*([\\s\\S]*?)\\}%%/i\n\n/** True if `line` is an init directive. */\nexport function isInitDirective(line: string): boolean {\n return INIT_DIRECTIVE.test(line)\n}\n\n/**\n * Turn Mermaid's relaxed JSON into strict JSON.\n *\n * Mermaid accepts `{theme: 'dark'}`; JSON.parse does not. Quoting is applied\n * only outside string literals so a value containing a colon, a brace, or an\n * apostrophe survives intact.\n */\nfunction normalizeRelaxedJson(text: string): string {\n let out = ''\n let quote: '\"' | \"'\" | null = null\n\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]!\n\n if (quote !== null) {\n if (ch === '\\\\' && i + 1 < text.length) {\n /*\n * Consume the escape and its target together. Without this, the\n * backslash is emitted bare and the quote it protects reads as the\n * end of the string — so strict, valid JSON like {\"theme\": \"a\\\"b\"}\n * re-emits unbalanced and the whole directive is dropped.\n *\n * JSON has no \\' escape, so a single-quoted string's \\' becomes a\n * bare apostrophe. Every other escape passes through untouched.\n */\n const next = text[i + 1]!\n out += next === \"'\" ? \"'\" : `\\\\${next}`\n i++\n } else if (ch === quote) {\n quote = null\n out += '\"'\n } else if (ch === '\"') {\n // A double quote inside a single-quoted string must be escaped once\n // the string is re-emitted with double quotes.\n out += '\\\\\"'\n } else {\n out += ch\n }\n continue\n }\n\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n out += '\"'\n continue\n }\n\n // Bare key: an identifier run followed (after spaces) by a colon.\n if (/[A-Za-z_$]/.test(ch)) {\n let j = i\n while (j < text.length && /[\\w$-]/.test(text[j]!)) j++\n const word = text.slice(i, j)\n let k = j\n while (k < text.length && /\\s/.test(text[k]!)) k++\n\n if (text[k] === ':') {\n out += `\"${word}\"`\n i = j - 1\n continue\n }\n\n // A bare word in value position — `true`, `false`, `null` are already\n // valid JSON; anything else is quoted so it parses as a string.\n out += /^(true|false|null)$/i.test(word)\n ? word.toLowerCase()\n : `\"${word}\"`\n i = j - 1\n continue\n }\n\n out += ch\n }\n\n return out\n}\n\n/**\n * Parse an init directive's payload.\n *\n * Returns `undefined` when the line is not a directive or its payload cannot\n * be parsed. A malformed directive is ignored rather than fatal: Mermaid\n * treats config as advisory, and failing an entire diagram over a stray brace\n * in a comment-like construct would be a poor trade.\n */\nexport function parseInitDirective(line: string): InitConfig | undefined {\n const match = line.match(INIT_DIRECTIVE)\n if (!match) return undefined\n\n let payload: unknown\n try {\n payload = JSON.parse(normalizeRelaxedJson(match[1]!))\n } catch {\n return undefined\n }\n\n if (typeof payload !== 'object' || payload === null) return undefined\n\n const config: InitConfig = { ignored: [] }\n const record: Record<string, unknown> = { ...payload }\n\n for (const [key, value] of Object.entries(record)) {\n const lower = key.toLowerCase()\n\n if (lower === 'theme' && typeof value === 'string') {\n /*\n * Kept on the result as metadata for a caller that wants to act on it,\n * but listed as ignored: it is parsed and deliberately not applied. See\n * IGNORED_KEYS for why.\n */\n config.theme = value\n config.ignored.push(key)\n continue\n }\n\n if (lower === 'flowchart' && typeof value === 'object' && value !== null) {\n const flowchart: Record<string, unknown> = { ...value }\n for (const [fk, fv] of Object.entries(flowchart)) {\n if (fk.toLowerCase() === 'curve' && typeof fv === 'string') {\n if (CURVE_STYLES.has(fv as CurveStyle)) {\n config.curve = fv as CurveStyle\n }\n continue\n }\n if (fk.toLowerCase() in IGNORED_KEYS) config.ignored.push(fk)\n }\n continue\n }\n\n if (lower in IGNORED_KEYS) config.ignored.push(key)\n }\n\n return config\n}\n\n/**\n * Scan a whole diagram for init directives and merge them.\n *\n * Later directives win over earlier ones, matching Mermaid, where a second\n * directive overrides the first.\n */\nexport function extractInitConfig(lines: string[]): InitConfig {\n const merged: InitConfig = { ignored: [] }\n\n for (const line of lines) {\n const config = parseInitDirective(line)\n if (!config) continue\n if (config.theme !== undefined) merged.theme = config.theme\n if (config.curve !== undefined) merged.curve = config.curve\n merged.ignored.push(...config.ignored)\n }\n\n return merged\n}\n\n/**\n * Fold an init config into render options.\n *\n * Caller-supplied options always win: a directive supplies a default, never\n * an override. See the module header for why.\n */\nexport function applyInitConfig(\n options: RenderOptions,\n config: InitConfig,\n): RenderOptions {\n if (config.curve === undefined || options.curve !== undefined) return options\n return { ...options, curve: config.curve }\n}\n\n/** Human-readable note about directive keys that were parsed but not applied. */\nexport function describeIgnored(config: InitConfig): string[] {\n return [...new Set(config.ignored)].map(\n (key) => `${key}: ${IGNORED_KEYS[key.toLowerCase()] ?? 'not supported'}`,\n )\n}\n","// ============================================================================\n// Text Metrics — Variable-width character measurement for SVG layout\n// ============================================================================\n//\n// Provides font-agnostic text width estimation using character class buckets.\n// More accurate than uniform character width for proportional fonts.\n//\n// Width ratios are normalized where 1.0 = average lowercase letter.\n// Final pixel width = sum(charWidths) * fontSize * baseRatio\n// ============================================================================\n\n/**\n * Exact advance widths for every printable ASCII character (U+0020–U+007E),\n * measured from the real Inter font (canvas measureText, headless Chrome,\n * weight 400) and normalized so that ratio × fontSize × baseRatio reproduces\n * the measured pixel advance. Completeness over this range is asserted by\n * the `getCharWidth` \"table completeness\" test in text-metrics.test.ts.\n *\n * The coarse character-class buckets below remain as fallback for\n * non-ASCII characters not in this table. The buckets systematically\n * underestimated Inter widths (spaces, m/w, digits, punctuation), and the\n * error grew with text length — wide edge labels overflowed their\n * 8px-padded background rects.\n */\nconst INTER_ADVANCES: Record<string, number> = {\n ' ': 0.521,\n '!': 0.533,\n '\"': 0.863,\n '#': 1.173,\n $: 1.188,\n '%': 1.818,\n '&': 1.193,\n \"'\": 0.555,\n '(': 0.675,\n ')': 0.675,\n '*': 0.928,\n '+': 1.225,\n ',': 0.534,\n '-': 0.852,\n '.': 0.534,\n '/': 0.667,\n '0': 1.168,\n '1': 0.753,\n '2': 1.129,\n '3': 1.144,\n '4': 1.196,\n '5': 1.099,\n '6': 1.148,\n '7': 1.048,\n '8': 1.146,\n '9': 1.148,\n ':': 0.534,\n ';': 0.559,\n '<': 1.225,\n '=': 1.225,\n '>': 1.225,\n '?': 0.947,\n '@': 1.789,\n A: 1.278,\n B: 1.212,\n C: 1.353,\n D: 1.336,\n E: 1.113,\n F: 1.093,\n G: 1.382,\n H: 1.376,\n I: 0.497,\n J: 1.057,\n K: 1.244,\n L: 1.047,\n M: 1.673,\n N: 1.395,\n O: 1.416,\n P: 1.183,\n Q: 1.416,\n R: 1.192,\n S: 1.188,\n T: 1.195,\n U: 1.378,\n V: 1.278,\n W: 1.825,\n X: 1.263,\n Y: 1.257,\n Z: 1.165,\n '[': 0.675,\n '\\\\': 0.667,\n ']': 0.675,\n '^': 0.873,\n _: 0.845,\n '`': 0.598,\n a: 1.04,\n b: 1.134,\n c: 1.058,\n d: 1.134,\n e: 1.08,\n f: 0.685,\n g: 1.136,\n h: 1.095,\n i: 0.448,\n j: 0.448,\n k: 1.016,\n l: 0.448,\n m: 1.622,\n n: 1.094,\n o: 1.11,\n p: 1.134,\n q: 1.134,\n r: 0.697,\n s: 0.977,\n t: 0.606,\n u: 1.095,\n v: 1.041,\n w: 1.515,\n x: 1.011,\n y: 1.041,\n z: 1.023,\n '{': 0.789,\n '|': 0.616,\n '}': 0.789,\n '~': 1.225,\n}\n\n/**\n * Whether `char` has an exact measured Inter advance in `INTER_ADVANCES`,\n * as opposed to falling through to a coarse character-class bucket or the\n * default width in `getCharWidth`. Exported only so tests can assert the\n * table covers every printable ASCII character (U+0020–U+007E) — a gap\n * here means a real character silently degrades to an approximate width.\n */\nexport function hasMeasuredAdvance(char: string): boolean {\n return Object.hasOwn(INTER_ADVANCES, char)\n}\n\n/**\n * Narrow characters - visually thin glyphs.\n * Note: '1' is included because in proportional fonts (like Inter), it's\n * significantly narrower than other digits which use tabular/uniform width.\n */\nconst NARROW_CHARS = new Set([\n 'i',\n 'l',\n 't',\n 'f',\n 'j',\n 'I',\n '1',\n '!',\n '|',\n '.',\n ',',\n ':',\n ';',\n \"'\",\n])\n\n/**\n * Wide characters - visually wide glyphs\n */\nconst WIDE_CHARS = new Set(['W', 'M', 'w', 'm', '@', '%'])\n\n/**\n * Very wide characters - widest Latin glyphs\n */\nconst VERY_WIDE_CHARS = new Set(['W', 'M'])\n\n/**\n * Semi-narrow punctuation - brackets and slashes are narrower than letters\n * but wider than narrow chars like dots/commas\n */\nconst SEMI_NARROW_PUNCT = new Set([\n '(',\n ')',\n '[',\n ']',\n '{',\n '}',\n '/',\n '\\\\',\n '-',\n '\"',\n '`',\n])\n\n/**\n * Check if a code point is a combining diacritical mark (zero-width overlay)\n */\nfunction isCombiningMark(code: number): boolean {\n // Combining Diacritical Marks: U+0300–U+036F\n // Combining Diacritical Marks Extended: U+1AB0–U+1AFF\n // Combining Diacritical Marks Supplement: U+1DC0–U+1DFF\n // Combining Diacritical Marks for Symbols: U+20D0–U+20FF\n // Combining Half Marks: U+FE20–U+FE2F\n return (\n (code >= 0x0300 && code <= 0x036f) ||\n (code >= 0x1ab0 && code <= 0x1aff) ||\n (code >= 0x1dc0 && code <= 0x1dff) ||\n (code >= 0x20d0 && code <= 0x20ff) ||\n (code >= 0xfe20 && code <= 0xfe2f)\n )\n}\n\n/**\n * Check if a code point is fullwidth (CJK, emoji, etc.)\n * These characters occupy approximately 2x the width of Latin letters.\n */\nfunction isFullwidth(code: number): boolean {\n // CJK Radicals Supplement: U+2E80–U+2EFF\n // Kangxi Radicals: U+2F00–U+2FDF\n // CJK Symbols and Punctuation: U+3000–U+303F\n // Hiragana: U+3040–U+309F\n // Katakana: U+30A0–U+30FF\n // Bopomofo: U+3100–U+312F\n // Hangul Compatibility Jamo: U+3130–U+318F\n // Kanbun: U+3190–U+319F\n // Bopomofo Extended: U+31A0–U+31BF\n // CJK Strokes: U+31C0–U+31EF\n // Katakana Phonetic Extensions: U+31F0–U+31FF\n // Enclosed CJK Letters and Months: U+3200–U+32FF\n // CJK Compatibility: U+3300–U+33FF\n // CJK Unified Ideographs Extension A: U+3400–U+4DBF\n // CJK Unified Ideographs: U+4E00–U+9FFF\n // Hangul Syllables: U+AC00–U+D7AF\n // CJK Compatibility Ideographs: U+F900–U+FAFF\n // Halfwidth and Fullwidth Forms (fullwidth part): U+FF00–U+FF60, U+FFE0–U+FFE6\n // CJK Unified Ideographs Extension B+: U+20000–U+2A6DF (and beyond)\n\n return (\n (code >= 0x1100 && code <= 0x115f) || // Hangul Jamo\n (code >= 0x2e80 && code <= 0x2eff) || // CJK Radicals Supplement\n (code >= 0x2f00 && code <= 0x2fdf) || // Kangxi Radicals\n (code >= 0x3000 && code <= 0x303f) || // CJK Symbols and Punctuation\n (code >= 0x3040 && code <= 0x309f) || // Hiragana\n (code >= 0x30a0 && code <= 0x30ff) || // Katakana\n (code >= 0x3100 && code <= 0x312f) || // Bopomofo\n (code >= 0x3130 && code <= 0x318f) || // Hangul Compatibility Jamo\n (code >= 0x3190 && code <= 0x31ff) || // Kanbun + extensions\n (code >= 0x3200 && code <= 0x33ff) || // Enclosed CJK + Compatibility\n (code >= 0x3400 && code <= 0x4dbf) || // CJK Extension A\n (code >= 0x4e00 && code <= 0x9fff) || // CJK Unified Ideographs\n (code >= 0xac00 && code <= 0xd7af) || // Hangul Syllables\n (code >= 0xf900 && code <= 0xfaff) || // CJK Compatibility Ideographs\n (code >= 0xff00 && code <= 0xff60) || // Fullwidth ASCII\n (code >= 0xffe0 && code <= 0xffe6) || // Fullwidth symbols\n code >= 0x20000 // CJK Extension B and beyond\n )\n}\n\n/**\n * Regex for emoji detection using Unicode property escapes.\n * Uses Emoji_Presentation and Extended_Pictographic (not just Emoji)\n * because \\p{Emoji} includes digits and # which we don't want as fullwidth.\n *\n * \\p{Extended_Pictographic} on its own is broader than \"renders wide\" — it\n * flags characters that are merely emoji-*capable* (could take a VS16\n * selector to become emoji), not characters that actually render wide by\n * default. Most such characters (e.g. ❤ U+2764) are still conventionally\n * treated as wide, so the OR is kept — except for the Geometric Shapes\n * block (U+25A0–U+25FF, see `isGeometricShapesTextDefault` below), which\n * this renderer itself uses for narrow box-drawing/arrowhead glyphs like\n * ▶ U+25B6 and ◀ U+25C0 (see src/ascii/sequence.ts); those render as a\n * single narrow column in every real terminal despite being flagged\n * Extended_Pictographic.\n */\nconst EMOJI_PRESENTATION_REGEX = /\\p{Emoji_Presentation}/u\nconst EXTENDED_PICTOGRAPHIC_REGEX = /\\p{Extended_Pictographic}/u\n\n/**\n * Geometric Shapes block (U+25A0–U+25FF). Characters here that also carry\n * Emoji_Presentation (e.g. ◽ ◾) are unaffected by this — they're already\n * caught by the Emoji_Presentation check in `isEmoji` before this applies.\n */\nfunction isGeometricShapesTextDefault(code: number): boolean {\n return code >= 0x25a0 && code <= 0x25ff\n}\n\n/**\n * Arrows-block glyphs that carry Extended_Pictographic (emoji-capable) but\n * no default Emoji_Presentation, and render as a single narrow column in\n * every real terminal — the same misclassification the Geometric Shapes\n * block above hits. Covers:\n *\n * - ↖ U+2196, ↗ U+2197, ↘ U+2198, ↙ U+2199 — drawn by the ASCII renderer\n * itself for diagonal edge-routing arrowheads\n * (packages/ascii-renderer/src/draw-arrows.ts's `drawArrowHead`) in place\n * of the filled triangles (◢◣◤◥) JetBrains Mono NL has no glyph for at\n * all — see issue #1062.\n * - ↔ U+2194, ↕ U+2195, ↩ U+21A9, ↪ U+21AA — not drawn by the renderer\n * itself, but reachable if a diagram author types one into node/edge/note\n * label text, which passes through to the ASCII grid verbatim.\n */\nfunction isArrowsBlockTextDefault(code: number): boolean {\n return (\n (code >= 0x2194 && code <= 0x2199) || code === 0x21a9 || code === 0x21aa\n )\n}\n\n/**\n * Check if a character is an emoji (fullwidth)\n */\nfunction isEmoji(char: string): boolean {\n if (EMOJI_PRESENTATION_REGEX.test(char)) return true\n const code = char.codePointAt(0)\n if (code !== undefined && isGeometricShapesTextDefault(code)) return false\n if (code !== undefined && isArrowsBlockTextDefault(code)) return false\n return EXTENDED_PICTOGRAPHIC_REGEX.test(char)\n}\n\n/**\n * Check if a single character (code point) is \"wide\" — i.e. renders as two\n * columns in a monospace terminal / two grid cells wide instead of one.\n * Covers CJK/kana/hangul/fullwidth-form characters (via `isFullwidth`) as\n * well as emoji. Shared by the SVG text-measurement path (`getCharWidth`\n * above) and the ASCII grid's display-width helpers\n * (`src/ascii/display-width.ts`), so both agree on what counts as wide.\n */\nexport function isWideChar(char: string): boolean {\n const code = char.codePointAt(0)\n if (code === undefined) return false\n return isFullwidth(code) || isEmoji(char)\n}\n\n/**\n * Get the relative width of a single character.\n *\n * Returns a normalized width ratio where:\n * - 0.0 = zero-width (combining marks)\n * - 0.3 = space\n * - 0.4 = narrow (i, l, t, f, j, I, 1)\n * - 0.8 = semi-narrow (r)\n * - 1.0 = average lowercase\n * - 1.2 = wide lowercase / uppercase\n * - 1.5 = very wide (W, M)\n * - 2.0 = fullwidth (CJK, emoji)\n */\nexport function getCharWidth(char: string): number {\n const code = char.codePointAt(0)\n if (code === undefined) return 0\n\n // Exact measured advance for printable ASCII (Inter). The own-property\n // check must come first: without it, bracket access on a plain object\n // literal also resolves inherited Object.prototype properties (e.g.\n // `INTER_ADVANCES['toString']` is a function, not undefined).\n if (Object.hasOwn(INTER_ADVANCES, char)) {\n const measured = INTER_ADVANCES[char]\n // The `undefined` case is unreachable: INTER_ADVANCES is a `Record<string,\n // number>` object literal whose every value is a numeric literal, and\n // `noUncheckedIndexedAccess` is what forces this check's type, not any\n // real possibility that an own property here holds `undefined`. Given\n // `Object.hasOwn` is already true, `measured` is always a defined\n // number — kept (not simplified to a bare `return measured`) so the\n // check still holds if a future entry's value type ever widens.\n /* v8 ignore else */\n if (measured !== undefined) return measured\n }\n\n // Zero-width: combining diacritical marks\n if (isCombiningMark(code)) return 0\n\n // Fullwidth: CJK, emoji\n if (isFullwidth(code) || isEmoji(char)) return 2.0\n\n // Accented Latin: use the base letter's measured advance (é → e, Ü → U)\n const base = char.normalize('NFD')[0]\n if (base !== undefined && base !== char) {\n const baseMeasured = INTER_ADVANCES[base]\n if (baseMeasured !== undefined) return baseMeasured\n }\n\n // Space\n if (char === ' ') return 0.3\n\n // Very wide Latin\n if (VERY_WIDE_CHARS.has(char)) return 1.5\n\n // Wide Latin\n if (WIDE_CHARS.has(char)) return 1.2\n\n // Narrow Latin\n if (NARROW_CHARS.has(char)) return 0.4\n\n // Semi-narrow punctuation (brackets, slashes, hyphens)\n if (SEMI_NARROW_PUNCT.has(char)) return 0.5\n\n // Semi-narrow letter\n if (char === 'r') return 0.8\n\n // Uppercase (slightly wider than lowercase on average)\n if (code >= 65 && code <= 90) return 1.2\n\n // Digits (uniform width in most fonts)\n if (code >= 48 && code <= 57) return 1.0\n\n // Default: average lowercase width\n return 1.0\n}\n\n/**\n * Measure the pixel width of a text string.\n *\n * Uses character class buckets for more accurate width estimation\n * than uniform character width assumptions.\n *\n * @param text - The text to measure\n * @param fontSize - Font size in pixels\n * @param fontWeight - Font weight (affects width slightly)\n * @returns Estimated width in pixels\n */\n/** Advance width of a monospace glyph, as a fraction of font size. */\nconst MONO_ADVANCE = 0.6\n\n/**\n * Whether text is measured with monospace metrics.\n *\n * The character buckets above model a proportional face (they are calibrated for Inter), but\n * `RenderOptions.font` accepts any family. Measuring a monospace font with them sizes every box\n * for the wrong glyph widths: narrow strings under-measure by ~60% and overflow, wide ones\n * over-measure by ~37%.\n *\n * The switch lives here rather than in `styles.ts` because this is the single choke point every\n * diagram type measures through — `estimateTextWidth` and `measureMultilineText` both land here,\n * and flowchart layout only reaches text metrics via the latter.\n */\nlet monospaceMetrics = false\n\n/** Fonts whose glyphs all share one advance width. */\nexport function isMonospaceFont(font: string): boolean {\n // `mono` unanchored so it also catches `ui-monospace`; `code` bounded so it does not fire on\n // unrelated names. \"Mona Sans\" is deliberately not a match.\n return /mono|consol|menlo|courier|\\bcode\\b/i.test(font)\n}\n\n/** Select the metrics model for subsequent measurements. Called once per render. */\nexport function setMonospaceMetrics(monospace: boolean): void {\n monospaceMetrics = monospace\n}\n\nexport function measureTextWidth(\n text: string,\n fontSize: number,\n fontWeight: number,\n): number {\n // Add minimum padding to prevent truncation at text boundaries\n // Increased from 0.1 to 0.15 for better label separation and collision prevention\n const minPadding = fontSize * 0.15\n\n if (monospaceMetrics) {\n let count = 0\n // Count code points, so surrogate pairs stay one cell wide.\n for (const _ of text) count += 1\n return count * fontSize * MONO_ADVANCE + minPadding\n }\n\n // Base ratio calibrated for Inter font family\n // Heavier weights are slightly wider\n // Added +0.02 buffer to prevent edge truncation of characters like 's' at line ends\n const baseRatio = fontWeight >= 600 ? 0.6 : fontWeight >= 500 ? 0.57 : 0.54\n\n let totalWidth = 0\n\n // Iterate over code points (handles surrogate pairs for emoji/CJK)\n for (const char of text) {\n totalWidth += getCharWidth(char)\n }\n\n return totalWidth * fontSize * baseRatio + minPadding\n}\n\n// ============================================================================\n// Multi-line Text Measurement\n// ============================================================================\n\n/** Standard line height ratio for multi-line text (1.3 = 130% of font size) */\nexport const LINE_HEIGHT_RATIO = 1.3\n\n/** Metrics for multi-line text measurement */\nexport interface MultilineMetrics {\n /** Maximum line width in pixels */\n width: number\n /** Total height in pixels (lines × lineHeight) */\n height: number\n /** Individual lines after splitting */\n lines: string[]\n /** Computed line height in pixels */\n lineHeight: number\n}\n\n/**\n * Measure multi-line text dimensions.\n *\n * Splits text on newlines and returns the maximum width across all lines,\n * total height based on line count, and the split lines for rendering.\n *\n * @param text - The text to measure (may contain \\n)\n * @param fontSize - Font size in pixels\n * @param fontWeight - Font weight (affects width slightly)\n * @returns Metrics including width, height, lines array, and lineHeight\n */\nexport function measureMultilineText(\n text: string,\n fontSize: number,\n fontWeight: number,\n): MultilineMetrics {\n const lines = text.split('\\n')\n const lineHeight = fontSize * LINE_HEIGHT_RATIO\n\n // Width = max of all line widths\n let maxWidth = 0\n for (const line of lines) {\n const plain = line.replace(/<\\/?(?:b|strong|i|em|u|s|del)\\s*>/gi, '')\n const w = measureTextWidth(plain, fontSize, fontWeight)\n if (w > maxWidth) maxWidth = w\n }\n\n return {\n width: maxWidth,\n height: lines.length * lineHeight,\n lines,\n lineHeight,\n }\n}\n","// ============================================================================\n// SVG numeric formatting — round interpolated coordinates/dimensions to 2dp.\n//\n// Ported from lukilabs/beautiful-mermaid#77 (\"Round all floats to 2 decimal\n// places in SVG output\", credit: GauBen). ELK layout and the curve/bezier\n// math in packages/svg-renderer/src/edge-curves.ts produce full\n// floating-point precision (e.g. `142.38427299999998`), which upstream's PR\n// pointed out bloats generated SVG for no visual benefit — nothing renders a\n// sub-hundredth-of-a-pixel difference. `f` is a tagged template literal used\n// in place of a plain template literal (`` f`<rect x=\"${x}\" />` `` instead of\n// `` `<rect x=\"${x}\" />` ``) everywhere flowchart/ER/class/sequence markup is\n// built, so every interpolated number gets rounded right where it is\n// stringified, with no change to non-numeric interpolations (ids, colors,\n// already-formatted strings).\n//\n// packages/svg-renderer/src/xychart/{renderer,layout}.ts already has its own\n// rounding (`r()`, 1dp) for unrelated axis-label-formatting reasons — this\n// helper is deliberately not applied there.\n// ============================================================================\n\n/**\n * Round a number to at most 2 decimal places for SVG output.\n *\n * Non-finite input (`NaN`/`Infinity`, reachable from degenerate zero-size\n * geometry — e.g. a normalized zero-length vector) is normalized to `0`\n * rather than stringified literally. This is a deliberate improvement over\n * upstream: `\"NaN\"`/`\"Infinity\"` are not valid SVG numeric attribute values,\n * so passing them through would hand the browser malformed markup instead of\n * just an odd-looking number. Every other case matches upstream exactly,\n * including collapsing `-0` to `0`.\n */\nfunction roundCoord(n: number): number {\n if (!Number.isFinite(n)) return 0\n return Number(n.toFixed(2))\n}\n\n/**\n * Tagged template literal: stringifies interpolated values, rounding any\n * `number` to at most 2 decimal places first. Non-number values (strings,\n * booleans, etc.) pass through `String()` unchanged, same as an untagged\n * template literal would.\n *\n * @example\n * f`<rect x=\"${3.14159}\" y=\"${-0.001}\" />` // '<rect x=\"3.14\" y=\"0\" />'\n */\nexport function f(strings: TemplateStringsArray, ...values: unknown[]): string {\n let out = strings[0] ?? ''\n for (let i = 0; i < values.length; i++) {\n const v = values[i]\n out += typeof v === 'number' ? String(roundCoord(v)) : String(v)\n out += strings[i + 1] ?? ''\n }\n return out\n}\n","// ============================================================================\n// Multi-line Text Rendering Utilities\n//\n// Shared utilities for rendering multi-line text in SVG using <tspan> elements.\n// Supports inline formatting: <b>, <i>, <u>, <s> mapped to SVG attributes.\n// Used across all diagram types (flowcharts, state, sequence, class, ER).\n// ============================================================================\n\nimport { LINE_HEIGHT_RATIO } from './text-metrics.ts'\nimport { f } from './render-utils.ts'\n\n/**\n * Normalize label text: strip surrounding quotes, convert <br> tags and\n * literal \\n sequences to newline characters. Strips unsupported HTML tags\n * but preserves formatting tags (<b>, <i>, <u>, <s>) for SVG rendering.\n */\nexport function normalizeBrTags(label: string): string {\n // Strip surrounding double quotes (Mermaid uses them for special chars in labels)\n const unquoted =\n label.startsWith('\"') && label.endsWith('\"') ? label.slice(1, -1) : label\n\n /*\n * Mermaid's markdown-string form wraps the label in backticks —\n * `A[\"` + '`**bold**`' + `\"]`. The markdown conversion below already runs\n * unconditionally, so the backticks only need removing; left in place they\n * rendered as literal characters in the label.\n */\n const unfenced =\n unquoted.length >= 2 && unquoted.startsWith('`') && unquoted.endsWith('`')\n ? unquoted.slice(1, -1)\n : unquoted\n\n return (\n unfenced\n .replace(/<br\\s*\\/?>/gi, '\\n')\n .replace(/\\\\n/g, '\\n')\n .replace(/<\\/?(?:sub|sup|small|mark)\\s*>/gi, '')\n // Markdown formatting → HTML tags (order matters: ** before *)\n .replace(/\\*\\*(.+?)\\*\\*/g, '<b>$1</b>')\n .replace(/(?<!\\*)\\*([^\\s*](?:[^*]*[^\\s*])?)\\*(?!\\*)/g, '<i>$1</i>')\n .replace(/~~(.+?)~~/g, '<s>$1</s>')\n )\n}\n\n/**\n * Strip all inline formatting tags from text, keeping only plain text.\n * Used for text measurement where tag characters shouldn't affect width.\n */\nexport function stripFormattingTags(text: string): string {\n return text.replace(/<\\/?(?:b|strong|i|em|u|s|del)\\s*>/gi, '')\n}\n\n/**\n * Escape special XML characters in text content.\n */\nexport function escapeXml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n/**\n * Escape a string for use as an XML/HTML attribute value.\n * Escapes quotes and ampersands to prevent attribute injection.\n *\n * Deliberately distinct from `escapeXml()` above: attribute values in this\n * codebase are always double-quoted, so single quotes don't need escaping —\n * `escapeXml()` escapes them too (for safe embedding in either quote style),\n * which would change output if reused here.\n */\nexport function escapeAttr(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\n// ============================================================================\n// Inline formatting: <b>, <i>, <u>, <s> → SVG tspan attributes\n// ============================================================================\n\ninterface StyledSegment {\n text: string\n bold: boolean\n italic: boolean\n underline: boolean\n strikethrough: boolean\n}\n\n/** Regex to match opening/closing formatting tags */\nconst FORMAT_TAG_REGEX = /<(\\/)?(?:(b|strong)|(i|em)|(u)|(s|del))\\s*>/gi\n\n/**\n * Parse a line of text into styled segments based on inline formatting tags.\n * Supports nesting: `<b>bold <i>both</i> bold</b>`.\n */\nfunction parseInlineFormatting(line: string): StyledSegment[] {\n const segments: StyledSegment[] = []\n let bold = false,\n italic = false,\n underline = false,\n strikethrough = false\n let lastIndex = 0\n\n // Reset lastIndex for global regex\n FORMAT_TAG_REGEX.lastIndex = 0\n\n let match: RegExpExecArray | null\n while ((match = FORMAT_TAG_REGEX.exec(line)) !== null) {\n // Capture text before this tag\n if (match.index > lastIndex) {\n segments.push({\n text: line.slice(lastIndex, match.index),\n bold,\n italic,\n underline,\n strikethrough,\n })\n }\n lastIndex = match.index + match[0].length\n\n const isClosing = Boolean(match[1])\n // match[2] = b|strong, match[3] = i|em, match[4] = u, match[5] = s|del\n if (match[2]) bold = !isClosing\n else if (match[3]) italic = !isClosing\n else if (match[4]) underline = !isClosing\n else if (match[5]) strikethrough = !isClosing\n }\n\n // Remaining text after last tag\n if (lastIndex < line.length) {\n segments.push({\n text: line.slice(lastIndex),\n bold,\n italic,\n underline,\n strikethrough,\n })\n }\n\n return segments\n}\n\n/** Check if a line contains any formatting tags */\nconst HAS_FORMAT_TAGS = /<\\/?(?:b|strong|i|em|u|s|del)\\s*>/i\n\n/**\n * Render a line's content as SVG, with inline formatting applied as tspan attributes.\n * Returns raw SVG content (no wrapping tspan — caller provides positioning).\n */\nfunction renderLineContent(line: string): string {\n // Fast path: no formatting tags\n if (!HAS_FORMAT_TAGS.test(line)) return escapeXml(line)\n\n const segments = parseInlineFormatting(line)\n if (segments.length === 0) return ''\n\n // If all segments are unstyled, just escape\n const allPlain = segments.every(\n (s) => !s.bold && !s.italic && !s.underline && !s.strikethrough,\n )\n if (allPlain) return segments.map((s) => escapeXml(s.text)).join('')\n\n return segments\n .map((seg) => {\n const escaped = escapeXml(seg.text)\n if (!seg.bold && !seg.italic && !seg.underline && !seg.strikethrough)\n return escaped\n\n const attrs: string[] = []\n if (seg.bold) attrs.push('font-weight=\"bold\"')\n if (seg.italic) attrs.push('font-style=\"italic\"')\n // SVG text-decoration can combine values\n const deco: string[] = []\n if (seg.underline) deco.push('underline')\n if (seg.strikethrough) deco.push('line-through')\n if (deco.length) attrs.push(`text-decoration=\"${deco.join(' ')}\"`)\n\n return `<tspan ${attrs.join(' ')}>${escaped}</tspan>`\n })\n .join('')\n}\n\n// ============================================================================\n// Multi-line text rendering\n// ============================================================================\n\n/**\n * Render a multi-line text element with proper vertical centering.\n *\n * For single-line text, returns a simple <text> element.\n * For multi-line text (containing \\n), returns <text> with <tspan> children.\n * Inline formatting tags (<b>, <i>, <u>, <s>) are rendered as SVG attributes.\n *\n * @param text - The text to render (may contain \\n and formatting tags)\n * @param cx - Center x coordinate\n * @param cy - Center y coordinate\n * @param fontSize - Font size in pixels\n * @param attrs - Additional SVG attributes (e.g., 'text-anchor=\"middle\" fill=\"var(--_text)\"')\n * @param baselineShift - Baseline shift for vertical alignment (default 0.35)\n * @returns SVG text element string\n */\nexport function renderMultilineText(\n text: string,\n cx: number,\n cy: number,\n fontSize: number,\n attrs: string,\n baselineShift: number = 0.35,\n): string {\n const lines = text.split('\\n')\n\n // Single line — simple text element\n if (lines.length === 1) {\n const dy = fontSize * baselineShift\n return f`<text x=\"${cx}\" y=\"${cy}\" ${attrs} dy=\"${dy}\">${renderLineContent(text)}</text>`\n }\n\n // Multi-line — use tspan elements with vertical centering\n const lineHeight = fontSize * LINE_HEIGHT_RATIO\n // First line dy: shift up by (n-1)/2 line heights, then add baseline shift\n const firstDy =\n -((lines.length - 1) / 2) * lineHeight + fontSize * baselineShift\n\n const tspans = lines\n .map((line, i) => {\n const dy = i === 0 ? firstDy : lineHeight\n return f`<tspan x=\"${cx}\" dy=\"${dy}\">${renderLineContent(line)}</tspan>`\n })\n .join('')\n\n return f`<text x=\"${cx}\" y=\"${cy}\" ${attrs}>${tspans}</text>`\n}\n\n/**\n * Render a multi-line text element with a background rectangle (pill).\n *\n * Used for edge labels that need a background for readability.\n *\n * @param text - The text to render (may contain \\n)\n * @param cx - Center x coordinate\n * @param cy - Center y coordinate\n * @param textWidth - Pre-calculated text width (max line width)\n * @param textHeight - Pre-calculated text height (lines × lineHeight)\n * @param fontSize - Font size in pixels\n * @param padding - Padding around text\n * @param textAttrs - SVG attributes for the text element\n * @param bgAttrs - SVG attributes for the background rect\n * @returns SVG elements string (rect + text)\n */\nexport function renderMultilineTextWithBackground(\n text: string,\n cx: number,\n cy: number,\n textWidth: number,\n textHeight: number,\n fontSize: number,\n padding: number,\n textAttrs: string,\n bgAttrs: string,\n): string {\n const bgWidth = textWidth + padding * 2\n const bgHeight = textHeight + padding * 2\n\n const rect =\n f`<rect x=\"${cx - bgWidth / 2}\" y=\"${cy - bgHeight / 2}\" ` +\n f`width=\"${bgWidth}\" height=\"${bgHeight}\" ${bgAttrs} />`\n\n const textEl = renderMultilineText(text, cx, cy, fontSize, textAttrs)\n\n return `${rect}\\n${textEl}`\n}\n","// ============================================================================\n// Style directives — `classDef`, `class A,B name`, `cssClass \"A,B\" name`,\n// `style A ...`, and the `A:::name` shorthand.\n//\n// Mermaid's flowchart and class diagrams share one styling grammar\n// (https://mermaid.js.org/syntax/flowchart.html#styling-and-classes,\n// https://mermaid.js.org/syntax/classDiagram.html#styling). The flowchart\n// parser grew the first implementation; this module is that implementation\n// lifted out so the class-diagram parser can reuse it rather than copy it\n// (issue #420). Both parsers feed a `StyleDirectives` bag, and both layouts\n// resolve a node's final style through `resolveNodeStyle` with the same\n// cascade — so a `classDef default` means the same thing in every diagram\n// type that supports it.\n// ============================================================================\n\n/** The three maps a styling-aware diagram carries. `MermaidGraph` and\n * `ClassDiagram` both satisfy this structurally. */\nexport interface StyleDirectives {\n /** Maps class names to their style properties (from `classDef name prop:val`) */\n classDefs: Map<string, Record<string, string>>\n /** Maps node IDs to their assigned class name (from `class A,B name`, `cssClass \"A,B\" name`, or `A:::name`) */\n classAssignments: Map<string, string>\n /** Maps node IDs to inline styles (from `style X fill:#f00,stroke:#333`) */\n nodeStyles: Map<string, Record<string, string>>\n}\n\n/** Parse \"fill:#f00,stroke:#333\" style property strings into a Record */\nexport function parseStyleProps(propsStr: string): Record<string, string> {\n // Strip trailing semicolons — Mermaid tolerates them (e.g. `stroke:#f00;`)\n const cleaned = propsStr.replace(/;\\s*$/, '')\n const props: Record<string, string> = {}\n for (const pair of cleaned.split(',')) {\n const colonIdx = pair.indexOf(':')\n if (colonIdx > 0) {\n const key = pair.slice(0, colonIdx).trim()\n const val = pair.slice(colonIdx + 1).trim()\n if (key && val) {\n props[key] = val\n }\n }\n }\n return props\n}\n\n/**\n * `classDef name prop:val,prop:val` — define a named style class.\n *\n * Mermaid's `classList` rule accepts a comma-separated list of names\n * (`classDef a,b font-size:12pt`) in both the flowchart and class grammars;\n * every name in the list gets the same properties.\n *\n * Returns true when `line` was a classDef statement (and has been applied).\n */\nexport function tryApplyClassDef(\n line: string,\n target: StyleDirectives,\n): boolean {\n const match = line.match(/^classDef\\s+([\\w,-]+)\\s+(.+)$/)\n if (!match) return false\n const props = parseStyleProps(match[2]!)\n for (const name of match[1]!.split(',')) {\n const trimmed = name.trim()\n if (trimmed) target.classDefs.set(trimmed, props)\n }\n return true\n}\n\n/**\n * `class A,B className` — attach a style class to one or more nodes.\n *\n * Whitespace around commas is allowed (`class A, B foo`) — the state-diagram\n * docs show that form (`class Moving, Crash movement`).\n *\n * Allows an optional trailing semicolon (`class A,B foo;`) — Mermaid treats\n * it as valid/optional, and `classDef`/`style` already tolerate it via their\n * `(.+)$` capture. Without this, the semicolon form fails to match here and\n * falls through to node parsing, rendering a stray node labelled \"class\".\n *\n * Returns true when `line` was a class-assignment statement.\n */\nexport function tryApplyClassAssignment(\n line: string,\n target: StyleDirectives,\n): boolean {\n const match = line.match(\n /^class\\s+([\\w-]+(?:\\s*,\\s*[\\w-]+)*)\\s+([\\w-]+)\\s*;?\\s*$/,\n )\n if (!match) return false\n const className = match[2]!\n for (const id of match[1]!.split(',')) {\n target.classAssignments.set(id.trim(), className)\n }\n return true\n}\n\n/**\n * `cssClass \"A,B\" className` — the class-diagram grammar's own attachment\n * form (`cssClassStatement: CSSCLASS STR ALPHA` in classDiagram.jison).\n * Same effect as `class A,B className`.\n *\n * Returns true when `line` was a cssClass statement.\n */\nexport function tryApplyCssClass(\n line: string,\n target: StyleDirectives,\n): boolean {\n const match = line.match(/^cssClass\\s+\"([^\"]*)\"\\s+([\\w-]+)\\s*;?\\s*$/)\n if (!match) return false\n const className = match[2]!\n for (const id of match[1]!.split(',')) {\n const trimmed = id.trim()\n if (trimmed) target.classAssignments.set(trimmed, className)\n }\n return true\n}\n\n/**\n * `style A,B fill:#f00,stroke:#333` — inline style on specific nodes.\n * Repeated `style` statements for the same node merge property by property.\n *\n * Returns true when `line` was a style statement.\n */\nexport function tryApplyStyleStatement(\n line: string,\n target: StyleDirectives,\n): boolean {\n const match = line.match(/^style\\s+([\\w,-]+)\\s+(.+)$/)\n if (!match) return false\n const props = parseStyleProps(match[2]!)\n for (const id of match[1]!.split(',').map((s) => s.trim())) {\n target.nodeStyles.set(id, { ...target.nodeStyles.get(id), ...props })\n }\n return true\n}\n\n/**\n * Split the `:::className` shorthand off a node/class identifier.\n *\n * `Animal:::someclass` → `{ id: 'Animal', className: 'someclass' }`;\n * an identifier without the shorthand comes back unchanged with no class.\n * Class names follow CSS identifier conventions (word characters and\n * hyphens), the same constraint the flowchart parser's CLASS_SHORTHAND_REGEX\n * applies.\n */\nexport function splitClassShorthand(identifier: string): {\n id: string\n className?: string\n} {\n const match = identifier.match(/^(.+?):::([\\w][\\w-]*)$/)\n if (!match) return { id: identifier }\n return { id: match[1]!, className: match[2]! }\n}\n\n/**\n * Resolve the final inline style for a node from classDefs and nodeStyles.\n *\n * Cascade, weakest to strongest:\n * 1. `classDef default` — Mermaid's implicit base for every node\n * 2. the node's own assigned class (`class A foo` / `A:::foo`)\n * 3. an explicit `style A ...` directive\n *\n * Returns undefined when nothing in the cascade applies, so callers can fall\n * back to theme defaults without an empty-object check.\n */\nexport function resolveNodeStyle(\n nodeId: string,\n directives: StyleDirectives,\n): Record<string, string> | undefined {\n let result: Record<string, string> | undefined\n\n /*\n * `classDef default` applies to every node without being assigned. It was\n * previously honored only when a node named it explicitly (`class X\n * default`), which silently diverges from Mermaid: a diagram styling all\n * its nodes via `classDef default` rendered unstyled with no error.\n */\n const defaultDef = directives.classDefs.get('default')\n if (defaultDef) {\n result = { ...defaultDef }\n }\n\n // Then the node's own class, overriding the default property by property.\n const className = directives.classAssignments.get(nodeId)\n if (className) {\n const classDef = directives.classDefs.get(className)\n if (classDef) {\n result = result ? { ...result, ...classDef } : { ...classDef }\n }\n }\n\n // Then, apply explicit style directives (override class styles)\n const nodeStyle = directives.nodeStyles.get(nodeId)\n if (nodeStyle) {\n result = result ? { ...result, ...nodeStyle } : { ...nodeStyle }\n }\n\n return result\n}\n\n/**\n * Validate a user-authored class name (from `:::className` or\n * `class A className`) before it's emitted into the SVG `class` attribute.\n *\n * The parsers already constrain class names to word characters and hyphens\n * (see `splitClassShorthand` above and the flowchart parser's\n * CLASS_SHORTHAND_REGEX), so this is a defense-in-depth allowlist rather\n * than an escaping step — a class name can't be made \"safe\" by escaping\n * since any character other than a valid CSS identifier character would\n * break the class token itself, not just the surrounding attribute quotes.\n * Anything that doesn't match a valid CSS identifier (letters, digits,\n * underscore, hyphen; not starting with a digit or a hyphen+digit) is\n * dropped rather than emitted.\n */\nexport function sanitizeClassName(\n className: string | undefined,\n): string | undefined {\n if (!className) return undefined\n return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(className) ? className : undefined\n}\n","/**\n * GENERATED FILE — do not edit by hand.\n *\n * Regenerate with `pnpm run build:mono-font`\n * (scripts/build-mono-font-subset.ts), which subsets\n * third_party/fonts/jetbrains-mono-nl/JetBrainsMonoNL-Regular.ttf down to\n * Basic Latin + Latin-1 Supplement — the self-hosted, embeddable\n * `@font-face` packages/core/src/theme.ts's `buildStyleBlock()` inlines\n * into an SVG's own `<style>` for its `.mono` rule (class-diagram method\n * signatures, ER-diagram attribute types), replacing a Google Fonts CDN\n * `@import` this library's SVG output previously depended on by default\n * (#1061). See that script's header comment for why this subset is\n * deliberately narrower than the site's own (demo/components/generated/\n * mono-font-subset.ts).\n */\n\n/** The self-hosted family name every consumer's font stack should list\n * first — matches this subset's own `name` table entry. */\nexport const MONO_FONT_FAMILY = 'JetBrains Mono NL'\n\n/** The `@font-face` rule embedding the subset as a base64 `woff2` data\n * URI — no separate network request, no page-depth-relative path to get\n * wrong (see the script header comment for why data-URI over a static\n * asset file). */\nexport const MONO_FONT_FACE_CSS = `@font-face {\n font-family: 'JetBrains Mono NL';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(data:font/woff2;base64,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) format('woff2');\n unicode-range: U+0020-007E, U+00A0-00FF;\n}`\n","// ============================================================================\n// Theme system — CSS custom property-based theming for mermaid SVG diagrams.\n//\n// Architecture:\n// - Two required variables: --bg (background) and --fg (foreground)\n// - Five optional enrichment variables: --line, --accent, --muted, --surface, --border\n// - Unset optionals fall back to color-mix() derivations from bg + fg\n// - All derived values computed in a <style> block inside the SVG\n//\n// This means the SVG is a function of its CSS variables. The caller provides\n// colors, and the SVG adapts. No light/dark mode detection needed.\n// ============================================================================\n\nimport { escapeXml, escapeAttr } from './multiline-utils.ts'\nimport { parseHexColor } from './color-utils.ts'\nimport { f } from './render-utils.ts'\nimport {\n MONO_FONT_FAMILY as SVG_MONO_FONT_FAMILY,\n MONO_FONT_FACE_CSS as SVG_MONO_FONT_FACE_CSS,\n} from './generated/mono-font-subset.ts'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * How the renderers emit their two inline-style surfaces — the `<style>`\n * element(s) and the root `<svg style=\"--bg: …\">` attribute. Both exist so a\n * host page with a strict `Content-Security-Policy` (`style-src` without\n * `'unsafe-inline'`) can still render diagrams with their colours intact;\n * see `RenderOptions.nonce` / `RenderOptions.styleAttribute` in packages/core/src/types.ts\n * and GitHub issue #216. Threaded as one object through every diagram\n * renderer so the two options can't drift apart per diagram type.\n */\nexport interface SvgEmitOptions {\n /** Value for a `nonce` attribute on every emitted `<style>` element. */\n nonce?: string\n /** Emit the root `style=\"--bg: …\"` attribute. Default: true. */\n styleAttribute?: boolean\n}\n\n/**\n * Diagram color configuration.\n *\n * Required: bg + fg give you a clean mono diagram.\n * Optional: line, accent, muted, surface, border bring in richer color\n * from Shiki themes or custom palettes. Each falls back to a color-mix()\n * derivation from bg + fg if not set.\n */\nexport interface DiagramColors {\n /** Background color → CSS variable --bg */\n bg: string\n /** Foreground / primary text color → CSS variable --fg */\n fg: string\n\n // -- Optional enrichment (each falls back to color-mix from bg+fg) --\n\n /** Edge/connector color → CSS variable --line */\n line?: string\n /** Arrow heads, highlights, special nodes → CSS variable --accent */\n accent?: string\n /** Secondary text, edge labels → CSS variable --muted */\n muted?: string\n /** Node/box fill tint → CSS variable --surface */\n surface?: string\n /** Node/group stroke color → CSS variable --border */\n border?: string\n}\n\n// ============================================================================\n// Defaults\n// ============================================================================\n\n/** Default bg/fg when no colors are provided (zinc light) */\nexport const DEFAULTS: Readonly<{ bg: string; fg: string }> = {\n bg: '#FFFFFF',\n fg: '#27272A',\n} as const\n\n// ============================================================================\n// color-mix() weights for derived CSS variables\n//\n// When an optional enrichment variable is NOT set, we compute the derived\n// value by mixing --fg into --bg at these percentages. This produces a\n// coherent mono hierarchy on any bg/fg combination.\n// ============================================================================\n\nexport const MIX = {\n /** Primary text: near-full fg */\n text: 100, // just use --fg directly\n /** Secondary text (group headers): fg mixed at 60% */\n textSec: 60,\n /** Muted text (edge labels, notes): fg mixed at 40% */\n textMuted: 40,\n /** Faint text (de-emphasized): fg mixed at 25% */\n textFaint: 25,\n /** Edge/connector lines: fg mixed at 50% for clear visibility */\n line: 50,\n /** Arrow head fill: fg mixed at 85% for clear visibility */\n arrow: 85,\n /** Node fill tint: fg mixed at 3% */\n nodeFill: 3,\n /** Node/group stroke: fg mixed at 20% */\n nodeStroke: 20,\n /** Group header band tint: fg mixed at 5% */\n groupHeader: 5,\n /** Inner divider strokes: fg mixed at 12% */\n innerStroke: 12,\n /** Key badge background opacity (ER diagrams) */\n keyBadge: 10,\n} as const\n\n// ============================================================================\n// Well-known theme palettes\n//\n// Curated bg/fg pairs (+ optional enrichment) for popular editor themes.\n// Users can also extract from Shiki theme objects via fromShikiTheme().\n// ============================================================================\n\nexport const THEMES: Record<string, DiagramColors> = {\n 'zinc-light': {\n bg: '#FFFFFF',\n fg: '#27272A',\n },\n 'zinc-dark': {\n bg: '#18181B',\n fg: '#FAFAFA',\n },\n 'tokyo-night': {\n bg: '#1a1b26',\n fg: '#a9b1d6',\n line: '#3d59a1',\n accent: '#7aa2f7',\n muted: '#565f89',\n },\n 'tokyo-night-storm': {\n bg: '#24283b',\n fg: '#a9b1d6',\n line: '#3d59a1',\n accent: '#7aa2f7',\n muted: '#565f89',\n },\n 'tokyo-night-light': {\n bg: '#d5d6db',\n fg: '#343b58',\n line: '#34548a',\n accent: '#34548a',\n muted: '#9699a3',\n },\n 'catppuccin-mocha': {\n bg: '#1e1e2e',\n fg: '#cdd6f4',\n line: '#585b70',\n accent: '#cba6f7',\n muted: '#6c7086',\n },\n 'catppuccin-latte': {\n bg: '#eff1f5',\n fg: '#4c4f69',\n line: '#9ca0b0',\n accent: '#8839ef',\n muted: '#9ca0b0',\n },\n nord: {\n bg: '#2e3440',\n fg: '#d8dee9',\n line: '#4c566a',\n accent: '#88c0d0',\n muted: '#616e88',\n },\n 'nord-light': {\n bg: '#eceff4',\n fg: '#2e3440',\n line: '#aab1c0',\n accent: '#5e81ac',\n muted: '#7b88a1',\n },\n dracula: {\n bg: '#282a36',\n fg: '#f8f8f2',\n line: '#6272a4',\n accent: '#bd93f9',\n muted: '#6272a4',\n },\n 'github-light': {\n bg: '#ffffff',\n fg: '#1f2328',\n line: '#d1d9e0',\n accent: '#0969da',\n muted: '#59636e',\n },\n 'github-dark': {\n bg: '#0d1117',\n fg: '#e6edf3',\n line: '#3d444d',\n accent: '#4493f8',\n muted: '#9198a1',\n },\n 'solarized-light': {\n bg: '#fdf6e3',\n fg: '#657b83',\n line: '#93a1a1',\n accent: '#268bd2',\n muted: '#93a1a1',\n },\n 'solarized-dark': {\n bg: '#002b36',\n fg: '#839496',\n line: '#586e75',\n accent: '#268bd2',\n muted: '#586e75',\n },\n 'one-dark': {\n bg: '#282c34',\n fg: '#abb2bf',\n line: '#4b5263',\n accent: '#c678dd',\n muted: '#5c6370',\n },\n} as const\n\nexport type ThemeName = keyof typeof THEMES\n\n// ============================================================================\n// Shiki theme extraction\n//\n// Extracts DiagramColors from a Shiki ThemeRegistrationResolved object.\n// This provides native compatibility with any VS Code / TextMate theme.\n// ============================================================================\n\n/**\n * Minimal subset of Shiki's ThemeRegistrationResolved that we need.\n * We don't import from shiki to avoid a hard dependency.\n */\ninterface ShikiThemeLike {\n type?: string\n colors?: Record<string, string>\n tokenColors?: Array<{\n scope?: string | string[]\n settings?: { foreground?: string }\n }>\n}\n\n/**\n * Extract diagram colors from a Shiki theme object.\n * Works with any VS Code / TextMate theme loaded by Shiki.\n *\n * Maps editor UI colors to diagram roles:\n * editor.background → bg\n * editor.foreground → fg\n * editorLineNumber.fg → line (optional)\n * focusBorder / keyword → accent (optional)\n * comment token → muted (optional)\n * editor.selectionBackground→ surface (optional)\n * editorWidget.border → border (optional)\n *\n * @example\n * ```ts\n * import { getSingletonHighlighter } from 'shiki'\n * import { fromShikiTheme } from 'zombie-mermaid'\n *\n * const hl = await getSingletonHighlighter({ themes: ['tokyo-night'] })\n * const colors = fromShikiTheme(hl.getTheme('tokyo-night'))\n * const svg = renderMermaidSVG(code, colors)\n * ```\n */\nexport function fromShikiTheme(theme: ShikiThemeLike): DiagramColors {\n const c = theme.colors ?? {}\n const dark = theme.type === 'dark'\n\n // Helper: find a token color by scope name\n const tokenColor = (scope: string): string | undefined =>\n theme.tokenColors?.find((t) =>\n Array.isArray(t.scope) ? t.scope.includes(scope) : t.scope === scope,\n )?.settings?.foreground\n\n return {\n bg: c['editor.background'] ?? (dark ? '#1e1e1e' : '#ffffff'),\n fg: c['editor.foreground'] ?? (dark ? '#d4d4d4' : '#333333'),\n line: c['editorLineNumber.foreground'] ?? undefined,\n accent: c['focusBorder'] ?? tokenColor('keyword') ?? undefined,\n muted:\n tokenColor('comment') ?? c['editorLineNumber.foreground'] ?? undefined,\n surface: c['editor.selectionBackground'] ?? undefined,\n border: c['editorWidget.border'] ?? undefined,\n }\n}\n\n// ============================================================================\n// SVG style block — the CSS variable derivation system\n//\n// Generates the <style> content that maps user-facing variables (--bg, --fg,\n// --line, etc.) to internal derived variables (--_text, --_line, etc.) using\n// color-mix() fallbacks.\n// ============================================================================\n\n/**\n * Characters that must never appear in a `font` value once it's embedded in\n * the generated `<style>` block — whether it's treated as a CSS `var()`\n * reference or as a literal font name. Angle brackets would let the value\n * break out of the `<style>...</style>` element (e.g. a literal `</style>`\n * or an injected `<script>`); semicolons/braces would let it terminate the\n * current CSS declaration/rule and inject new ones.\n */\nconst UNSAFE_FONT_CHARS_RE = /[<>{};]/\n\n/**\n * Detect a syntactically well-formed CSS `var()` reference, e.g.\n * `var(--font-family-body)` or `var(--font, 'Fallback Font')` (a `var()`\n * with a quoted fallback argument). Requires balanced parens and rejects\n * any of `UNSAFE_FONT_CHARS_RE`.\n *\n * `font.startsWith('var(')` alone isn't enough: a caller could pass\n * something that merely starts with `var(` but isn't actually one (e.g.\n * `var(--x); } .evil{...}`). Anything that fails this stricter check is\n * treated as an untrusted/malformed value and falls back to being quoted\n * as a literal font name below — which neutralizes it, since a quoted\n * string isn't parsed as a `var()` call by CSS.\n */\nfunction isSafeCssVarReference(font: string): boolean {\n const trimmed = font.trim()\n if (!trimmed.startsWith('var(') || !trimmed.endsWith(')')) return false\n if (UNSAFE_FONT_CHARS_RE.test(trimmed)) return false\n let depth = 0\n for (const ch of trimmed) {\n if (ch === '(') depth++\n else if (ch === ')') {\n depth--\n if (depth < 0) return false\n }\n }\n return depth === 0\n}\n\n/**\n * CSS generic font-family keywords. These are resolved by the browser/user\n * agent to a locally available font and are never real font names a Google\n * Fonts `@import` could fetch.\n */\nconst GENERIC_FONT_FAMILIES = new Set([\n 'sans-serif',\n 'serif',\n 'monospace',\n 'system-ui',\n 'ui-sans-serif',\n 'ui-serif',\n 'ui-monospace',\n 'ui-rounded',\n 'cursive',\n 'fantasy',\n 'math',\n 'emoji',\n 'fangsong',\n])\n\n/**\n * Detect a `font` value that names a font *stack* (comma-separated list,\n * e.g. `\"ui-sans-serif, system-ui, sans-serif\"`) or a single CSS generic\n * family keyword (e.g. `\"system-ui\"`) rather than a single concrete font\n * name. Neither is something a Google Fonts `@import` could ever\n * successfully fetch: a stack has no single `family=` value to request, and\n * a generic keyword is resolved locally by the browser, not hosted by\n * Google Fonts. Skipping the `@import` for these avoids baking a dead,\n * always-404 request into the SVG (see #223).\n */\nfunction isFontStackOrGenericFamily(font: string): boolean {\n const trimmed = font.trim()\n if (trimmed.includes(',')) return true\n return GENERIC_FONT_FAMILIES.has(trimmed.toLowerCase())\n}\n\n/**\n * The opening `<style>` tag every renderer uses — with a `nonce` attribute\n * when the caller supplied one (see `RenderOptions.nonce`, #216).\n *\n * All `<style>` emission points go through this one function so a nonce\n * can't be missed on one of them: under a nonce-based CSP a single\n * un-nonced `<style>` is silently dropped by the browser, which for the\n * theme block means an unstyled diagram with no console hint as to why.\n *\n * An empty/whitespace-only nonce is treated as unset — `nonce=\"\"` would\n * authorise nothing and only mislead a reader into thinking it did.\n * The value is attribute-escaped; a real nonce is base64 and never needs\n * escaping, but the option is a plain string and this keeps a stray `\"`\n * from breaking out of the attribute.\n */\nexport function styleOpenTag(nonce?: string): string {\n if (nonce === undefined || nonce.trim() === '') return '<style>'\n return `<style nonce=\"${escapeAttr(nonce)}\">`\n}\n\n/**\n * Build the CSS variable derivation rules for the SVG <style> block.\n *\n * When an optional variable (--line, --accent, etc.) is set on the SVG or\n * a parent element, it's used directly. When unset, the fallback computes\n * a blended value from --fg and --bg using color-mix().\n *\n * `nonce`, when given, lands on the `<style>` element as a `nonce`\n * attribute — see `styleOpenTag()`.\n *\n * `font` is user-supplied input. When it's a CSS `var(...)` reference (e.g.\n * `var(--font-family-body)`, letting the SVG inherit a host page's\n * design-system font), the Google Fonts `@import` is skipped — it would\n * otherwise URL-encode the literal `var(...)` string into a `family=` query\n * param and produce a guaranteed no-op request — and the value is emitted\n * unquoted, since a quoted string isn't parsed as a `var()` call by CSS.\n *\n * The `@import` is likewise skipped when `font` is a font *stack* (a\n * comma-separated list, e.g. `\"ui-sans-serif, system-ui, sans-serif\"`) or a\n * single CSS generic family keyword (e.g. `\"system-ui\"`) — see\n * `isFontStackOrGenericFamily`. Neither names a single concrete font that\n * Google Fonts could host, so importing one always produces a dead,\n * always-404 request (#223). This is additive to the `var()` skip above,\n * and only affects the `@import` decision — the `text { font-family: ... }`\n * rule still renders the literal value exactly as it does today.\n *\n * Any other value is treated as a literal font name: sanitized and quoted\n * exactly as before.\n *\n * `hasMonoFont`'s `.mono` rule (class-diagram method signatures, ER-diagram\n * attribute types) never reaches out to Google Fonts at all — it used to\n * `@import` 'JetBrains Mono' from Google's CDN, a third-party network\n * dependency a published library's default SVG output had no business\n * making (#1061, filed as a follow-up from #1059's ASCII-side self-hosting\n * fix). It now embeds {@link SVG_MONO_FONT_FACE_CSS} — a small, subsetted\n * (Basic Latin + Latin-1 Supplement), self-hosted `@font-face` as a base64\n * `woff2` data URI, generated by scripts/build-mono-font-subset.ts into\n * ./generated/mono-font-subset.ts — directly in the SVG's own `<style>`\n * block. Unlike `font` above, this isn't caller-configurable: it's the same\n * embed for every consumer, on by default, with no network request either\n * way — a strict improvement over the dead-or-alive CDN fetch it replaces,\n * and the only way a *standalone* SVG (no surrounding host page providing\n * its own copy of the font) renders `.mono` text correctly out of the box.\n */\nexport function buildStyleBlock(\n font: string,\n hasMonoFont: boolean,\n nonce?: string,\n): string {\n const isVarReference = isSafeCssVarReference(font)\n // Literal font names are sanitized before quoting (strip characters that\n // could break out of the CSS string or the <style> block). A validated\n // var() reference is passed through as-is so `var(--x, 'Fallback')` keeps\n // its own internal quotes intact.\n const safeFont = isVarReference ? font.trim() : font.replace(/[<>{};'\"]/g, '')\n const skipImport = isVarReference || isFontStackOrGenericFamily(font)\n\n const fontImports = skipImport\n ? []\n : [\n `@import url('https://fonts.googleapis.com/css2?family=${encodeURIComponent(safeFont)}:wght@400;500;600;700&display=swap');`,\n ]\n\n // Derived CSS variables: use override if set, else mix from bg+fg.\n // The --_ prefix signals \"private/derived\" — not meant for external override.\n const derivedVars = `\n /* Derived from --bg and --fg (overridable via --line, --accent, etc.) */\n --_text: var(--fg);\n --_text-sec: var(--muted, color-mix(in srgb, var(--fg) ${MIX.textSec}%, var(--bg)));\n --_text-muted: var(--muted, color-mix(in srgb, var(--fg) ${MIX.textMuted}%, var(--bg)));\n --_text-faint: color-mix(in srgb, var(--fg) ${MIX.textFaint}%, var(--bg));\n --_line: var(--line, color-mix(in srgb, var(--fg) ${MIX.line}%, var(--bg)));\n --_arrow: var(--accent, color-mix(in srgb, var(--fg) ${MIX.arrow}%, var(--bg)));\n --_node-fill: var(--surface, color-mix(in srgb, var(--fg) ${MIX.nodeFill}%, var(--bg)));\n --_node-stroke: var(--border, color-mix(in srgb, var(--fg) ${MIX.nodeStroke}%, var(--bg)));\n --_group-fill: var(--bg);\n --_group-hdr: color-mix(in srgb, var(--fg) ${MIX.groupHeader}%, var(--bg));\n --_inner-stroke: color-mix(in srgb, var(--fg) ${MIX.innerStroke}%, var(--bg));\n --_key-badge: color-mix(in srgb, var(--fg) ${MIX.keyBadge}%, var(--bg));`\n\n return [\n styleOpenTag(nonce),\n ...(fontImports.length > 0 ? [` ${fontImports.join('\\n ')}`] : []),\n // Self-hosted, embedded @font-face for `.mono` text — see the doc\n // comment above for why this never goes through the CDN-import/skip\n // logic `font` above does.\n ...(hasMonoFont ? [` ${SVG_MONO_FONT_FACE_CSS}`] : []),\n ` text { font-family: ${isVarReference ? safeFont : `'${safeFont}'`}, system-ui, sans-serif; }`,\n ...(hasMonoFont\n ? [\n ` .mono { font-family: '${SVG_MONO_FONT_FAMILY}', 'SF Mono', 'Fira Code', ui-monospace, monospace; }`,\n ]\n : []),\n ` svg {${derivedVars}`,\n ` }`,\n '</style>',\n ].join('\\n')\n}\n\n// ============================================================================\n// Fill → text color contrast\n//\n// When a node has a custom `fill` (from classDef/style) but no explicit\n// `color`, defaulting text to the ambient theme foreground can be unreadable\n// (e.g. white text on a light pastel fill in dark mode). If the fill is a\n// concrete, resolvable color (hex), compute a readable black/white text\n// color from its perceptual luminance. Anything else (CSS variable\n// references, named colors, malformed values) is left alone — the caller\n// falls back to the theme foreground.\n// ============================================================================\n\n/**\n * Perceptual luminance of an sRGB color on a 0-1 scale (0 = black, 1 =\n * white), using the ITU-R BT.601 luma weights (0.299/0.587/0.114).\n *\n * This is the simple, non-gamma-corrected \"perceived brightness\" formula\n * (as opposed to the gamma-corrected WCAG relative-luminance formula used\n * for contrast-ratio math). It's the right tool here: we just need a\n * black-vs-white pick, and BT.601 luma crosses black/white legibility at\n * ~0.5 on its own 0-1 scale, so the threshold in getReadableTextColor()\n * lines up directly with the formula instead of needing a separately\n * derived crossover constant. (True WCAG relative luminance would need a\n * ~0.18 threshold, not 0.5, to give the same black/white picks — see\n * https://www.w3.org/TR/WCAG20/#relativeluminancedef.)\n */\nfunction perceptualLuminance(r: number, g: number, b: number): number {\n return (0.299 * r + 0.587 * g + 0.114 * b) / 255\n}\n\n/**\n * Pick a readable text color for a given fill color.\n *\n * If `fill` is a concrete, resolvable hex color, returns pure black or\n * white depending on the fill's perceptual luminance (threshold 0.5: light\n * fills get dark text, dark fills get light text). If `fill` is undefined\n * or isn't a resolvable color (a `var(...)` reference, a named CSS color,\n * or malformed input), returns `fallback` unchanged so callers don't guess\n * at colors they can't actually evaluate.\n */\nexport function getReadableTextColor(\n fill: string | undefined,\n fallback: string,\n): string {\n if (!fill) return fallback\n const rgb = parseHexColor(fill)\n if (!rgb) return fallback\n const luminance = perceptualLuminance(...rgb)\n return luminance > 0.5 ? '#000000' : '#FFFFFF'\n}\n\n/**\n * Page-unique suffix generator for the root accessible-name `<title>` id.\n *\n * Mirrors `markerSuffix()` in packages/svg-renderer/src/renderer.ts, which solves the same\n * \"multiple SVGs inlined into one HTML page share a single id namespace\"\n * problem for arrow-marker ids — a marker's id is sanitized from its stroke\n * color, since two markers with the same color are meant to share one\n * definition. A title has no such natural per-call input (two diagrams can\n * easily have the same title text and must still get distinct ids), so this\n * increments a module-level counter instead: every `svgOpenTag()` call that\n * renders a title gets a fresh, never-repeated id for the lifetime of the\n * process, which is exactly what's needed for `aria-labelledby` to resolve\n * correctly when several rendered diagrams are concatenated into one page.\n */\nlet titleIdCounter = 0\n\nfunction nextTitleId(): string {\n titleIdCounter += 1\n return `zm-title-${titleIdCounter}`\n}\n\n/**\n * Test-only: reset the title-id counter so id assertions in tests don't\n * depend on how many titled diagrams earlier tests in the same file rendered.\n * Not part of the public API (not re-exported from src/index.ts).\n */\nexport function __resetSvgTitleIdCounterForTests(): void {\n titleIdCounter = 0\n}\n\n/**\n * The exact CSS declaration list the root `<svg style=\"…\">` attribute\n * carries: the theme custom properties (`--bg`, `--fg`, plus whichever\n * enrichment variables were actually provided — unset ones fall back to the\n * color-mix() derivations in the `<style>` block) and, unless `transparent`,\n * a `background: var(--bg)`.\n *\n * Exposed (via `themeCssVariables()` in src/index.ts) so a host that turns\n * the attribute off with `styleAttribute: false` — because its\n * Content-Security-Policy forbids `style=` attributes, which a nonce can\n * never authorise (#216) — can put the very same declarations in its own\n * stylesheet on a wrapper instead. Single source of truth for the variable\n * list: `svgOpenTag()` and the helper both call this, so the two can't\n * drift.\n *\n * Emitted compactly (`--bg:#fff;--fg:#000;background:var(--bg)`) rather\n * than pretty-printed: it's the same string the attribute has always\n * carried, so default output stays byte-identical. Colour values are the\n * caller's own `RenderOptions` and are not escaped here — same as before.\n */\nexport function themeStyleDeclarations(\n colors: DiagramColors,\n transparent?: boolean,\n): string {\n const vars = [\n `--bg:${colors.bg}`,\n `--fg:${colors.fg}`,\n colors.line ? `--line:${colors.line}` : '',\n colors.accent ? `--accent:${colors.accent}` : '',\n colors.muted ? `--muted:${colors.muted}` : '',\n colors.surface ? `--surface:${colors.surface}` : '',\n colors.border ? `--border:${colors.border}` : '',\n ]\n .filter(Boolean)\n .join(';')\n\n const bgStyle = transparent ? '' : ';background:var(--bg)'\n return `${vars}${bgStyle}`\n}\n\n/**\n * Build the SVG opening tag with CSS variables set as inline styles.\n * Only includes optional variables that are actually provided — unset ones\n * will fall back to the color-mix() derivations in the <style> block.\n *\n * `styleAttribute: false` drops the `style=\"…\"` attribute entirely (the\n * host supplies those declarations from its own stylesheet — see\n * `themeStyleDeclarations()` and `RenderOptions.styleAttribute`, #216).\n * Every other attribute — `xmlns`, `viewBox`, `width`/`height`, the\n * `role`/`aria-*` accessibility attributes, and anything a caller splices\n * on afterwards (`data-src`, `data-xychart-colors`) — is unaffected.\n *\n * Also handles the SVG's accessible name (see `RenderOptions.title` /\n * `RenderOptions.decorative` in packages/core/src/types.ts, and GitHub issue #215):\n *\n * - `decorative: true` → `aria-hidden=\"true\"` on the root, no `role` or\n * name. Use for a diagram already described in surrounding prose; `title`\n * is ignored when this is set.\n * - `title` given (and not decorative) → `role=\"img\"` +\n * `aria-labelledby=\"zm-title-N\"` on the root, plus a `<title id=\"zm-title-N\">`\n * as the SVG's first child holding the escaped text. This is the standard\n * SVG/WAI-ARIA \"accessible name via a referenced title element\" technique\n * (see MDN \"SVG accessibility\" and the WAI-ARIA `img` role: an element\n * with `role=\"img\"` needs a computed accessible name to be meaningful to\n * assistive tech; `aria-labelledby` pointing at a `<title>` is the\n * documented way to supply one for inline SVG).\n * - Neither given → `role=\"img\"` only, no name. This still stops assistive\n * tech from treating the SVG as a plain group (which announces every\n * node/edge label individually, out of reading order — the core bug in\n * #215), without fabricating a name the library can't honestly claim. It\n * reads the same as an `<img>` with no `alt`: present as a single image,\n * unlabeled.\n *\n * `hasInteractiveLinks` overrides all of the above (see #239): a `click A\n * \"url\"` statement renders a real, focusable `<a href>` inside the SVG, and\n * both `role=\"img\"` and `aria-hidden=\"true\"` are unsafe on an ancestor of a\n * focusable element — `role=\"img\"` tells assistive tech to stop descending\n * into children (the link becomes unreachable by screen reader while still\n * Tab-focusable), and `aria-hidden=\"true\"` on a focusable descendant is an\n * explicit WAI-ARIA violation (the link vanishes from the accessibility tree\n * but not from the tab order). When any node has a link, the root gets no\n * `role` at all — `decorative` is silently overridden rather than honored,\n * since aria-hiding a diagram that contains an actionable link is not a safe\n * request to fulfill — but `title`/`aria-labelledby` still apply if given:\n * an accessible name is still valid on an element with no explicit role.\n *\n * The per-node/per-point `<title>` tooltips added by earlier work (nested\n * inside `<g>` elements, with no `id` attribute) are unaffected: they never\n * collide with the root title's generated id, and a root `<title>` alongside\n * unrelated descendant `<title>` elements is valid SVG.\n *\n * @param transparent - If true, omits the background style for transparent SVGs\n * @param title - Accessible name text. Ignored when `decorative` is true\n * and there are no interactive links (see `hasInteractiveLinks`).\n * @param decorative - Marks the SVG as decorative (`aria-hidden=\"true\"`).\n * Overridden when `hasInteractiveLinks` is true.\n * @param hasInteractiveLinks - True if any node has a `click`-based `href`.\n * See #239 — forces no root `role` so the link\n * stays reachable, regardless of `decorative`.\n * @param styleAttribute - Emit the root `style=\"…\"` attribute. Default true.\n */\nexport function svgOpenTag(\n width: number,\n height: number,\n colors: DiagramColors,\n transparent?: boolean,\n title?: string,\n decorative?: boolean,\n hasInteractiveLinks?: boolean,\n styleAttribute: boolean = true,\n): string {\n const styleAttr = styleAttribute\n ? ` style=\"${themeStyleDeclarations(colors, transparent)}\"`\n : ''\n\n let a11yAttrs = ''\n let titleEl = ''\n if (hasInteractiveLinks) {\n // No root role: role=\"img\" or aria-hidden would hide a real, focusable\n // <a href> descendant from assistive tech while leaving it Tab-reachable.\n if (title) {\n const id = nextTitleId()\n a11yAttrs = ` aria-labelledby=\"${id}\"`\n titleEl = `\\n <title id=\"${id}\">${escapeXml(title)}</title>`\n }\n } else if (decorative) {\n a11yAttrs = ' aria-hidden=\"true\"'\n } else {\n a11yAttrs = ' role=\"img\"'\n if (title) {\n const id = nextTitleId()\n a11yAttrs += ` aria-labelledby=\"${id}\"`\n titleEl = `\\n <title id=\"${id}\">${escapeXml(title)}</title>`\n }\n }\n\n return (\n f`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${width} ${height}\" ` +\n f`width=\"${width}\" height=\"${height}\"${a11yAttrs}${styleAttr}>` +\n titleEl\n )\n}\n"],"mappings":"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"}
|