1/**
2 * react-native-svg applies neither the CSS custom properties Pele colors with
3 * nor CSS-only geometry such as `rx` in a style, so both become attributes.
4 * `theme` sets `--pele-*` values; the rest keep Pele's fallbacks.
5 */
6export function mermaidSvgForNative(svg: string, theme: Readonly<Record<string, string>>): string {
7 const rootTag = /^<svg\b[^>]*>/.exec(svg)?.[0];
8 if (!rootTag) return svg;
9 const declared = styleDeclarations(/\sstyle="([^"]*)"/.exec(rootTag)?.[1] ?? "");
10 const resolve = (value: string, depth = 0): string =>
11 depth > 8
12 ? value
13 : value.replace(
14 /var\((--[\w-]+)(?:,\s*((?:[^()]|\([^()]*\))*))?\)/g,
15 (_match, name: string, fallback: string | undefined) =>
16 resolve(theme[name] ?? declared.get(name) ?? fallback ?? "", depth + 1),
17 );
18
19 const fontSize = /(\d+(?:\.\d+)?)px/.exec(declared.get("font") ?? "")?.[1] ?? "16";
20 const fontFamily = resolve(declared.get("font-family") ?? "sans-serif").replaceAll('"', "'");
21 const root = resolve(rootTag.replace(/\sstyle="[^"]*"/, "")).replace(
22 />$/,
23 ` font-family="${fontFamily}" font-size="${fontSize}">`,
24 );
25 const body = svg
26 .slice(rootTag.length)
27 .replace(/\sstyle="([^"]*)"/g, (_match, style: string) =>
28 [...styleDeclarations(style)]
29 .map(([name, value]) => ` ${name}="${resolve(value).replace(/^(-?[\d.]+)px$/, "$1")}"`)
30 .join(""),
31 );
32 // Attribute values only: a label's text can itself read `var(--…)`.
33 return (
34 root +
35 body.replace(
36 /(\s[\w:-]+=")([^"]*)"/g,
37 (_match, prefix: string, value: string) => `${prefix}${resolve(value)}"`,
38 )
39 );
40}
41
42/** Fills the diagram's viewBox, so an exported image reads on any background. */
43export function withDiagramBackground(svg: string, color: string): string {
44 const rootTag = /^<svg\b[^>]*>/.exec(svg)?.[0];
45 const box = /\sviewBox="([^"]*)"/
46 .exec(rootTag ?? "")?.[1]
47 ?.trim()
48 .split(/[\s,]+/);
49 if (!rootTag || box?.length !== 4) return svg;
50 const [x, y, width, height] = box;
51 return `${rootTag}<rect x="${x}" y="${y}" width="${width}" height="${height}" fill="${color}"/>${svg.slice(rootTag.length)}`;
52}
53
54function styleDeclarations(style: string): Map<string, string> {
55 const declarations = new Map<string, string>();
56 for (const declaration of style.split(";")) {
57 const colon = declaration.indexOf(":");
58 if (colon > 0) {
59 declarations.set(declaration.slice(0, colon).trim(), declaration.slice(colon + 1).trim());
60 }
61 }
62 return declarations;
63}