@pixldocs/canvas-renderer 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,379 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const NON_RECOLORABLE = /* @__PURE__ */ new Set([
4
+ "none",
5
+ "transparent",
6
+ "currentcolor",
7
+ "inherit",
8
+ "initial",
9
+ "unset"
10
+ ]);
11
+ let colorCtx;
12
+ function getColorContext() {
13
+ if (colorCtx !== void 0) return colorCtx;
14
+ if (typeof document === "undefined") {
15
+ colorCtx = null;
16
+ return colorCtx;
17
+ }
18
+ colorCtx = document.createElement("canvas").getContext("2d");
19
+ return colorCtx;
20
+ }
21
+ function normalizeSvgColor(value) {
22
+ if (!value) return null;
23
+ const raw = value.trim().toLowerCase();
24
+ if (!raw || NON_RECOLORABLE.has(raw) || raw.startsWith("url(") || raw.startsWith("var(")) return null;
25
+ if (/^#[0-9a-f]{3}$/.test(raw)) {
26
+ return `#${raw[1]}${raw[1]}${raw[2]}${raw[2]}${raw[3]}${raw[3]}`;
27
+ }
28
+ if (/^#[0-9a-f]{6}$/.test(raw)) return raw;
29
+ if (/^#[0-9a-f]{8}$/.test(raw)) return raw.slice(0, 7);
30
+ const rgb = raw.match(/^rgba?\(([^)]+)\)$/);
31
+ if (rgb) {
32
+ const body = rgb[1].trim();
33
+ const colorPortion = body.includes("/") ? body.split("/")[0].trim() : body;
34
+ const commaParts = colorPortion.split(",").map((p) => p.trim()).filter(Boolean);
35
+ const parts = commaParts.length >= 3 ? commaParts : colorPortion.split(/\s+/).map((p) => p.trim()).filter(Boolean);
36
+ if (parts.length >= 3) {
37
+ const parseRgbComponent = (token) => {
38
+ const t = token.trim();
39
+ if (!t) return null;
40
+ if (t.endsWith("%")) {
41
+ const pct = Number.parseFloat(t.slice(0, -1));
42
+ if (!Number.isFinite(pct)) return null;
43
+ return Math.max(0, Math.min(255, Math.round(pct / 100 * 255)));
44
+ }
45
+ const num = Number.parseFloat(t);
46
+ if (!Number.isFinite(num)) return null;
47
+ return Math.max(0, Math.min(255, Math.round(num)));
48
+ };
49
+ const rgbNums = parts.slice(0, 3).map(parseRgbComponent);
50
+ if (rgbNums.every((n) => n != null)) {
51
+ const [r, g, b] = rgbNums;
52
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
53
+ }
54
+ }
55
+ }
56
+ const ctx = getColorContext();
57
+ if (ctx) {
58
+ try {
59
+ ctx.fillStyle = "#000000";
60
+ ctx.fillStyle = raw;
61
+ const parsed = String(ctx.fillStyle).toLowerCase();
62
+ if (/^#[0-9a-f]{6}$/.test(parsed)) return parsed;
63
+ const parsedRgb = parsed.match(/^rgba?\(([^)]+)\)$/);
64
+ if (parsedRgb) {
65
+ const parts = parsedRgb[1].split(",").map((p) => p.trim());
66
+ const nums = parts.slice(0, 3).map((p) => Number.parseFloat(p));
67
+ if (nums.every((n) => Number.isFinite(n))) {
68
+ const [r, g, b] = nums.map((n) => Math.max(0, Math.min(255, Math.round(n))));
69
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
70
+ }
71
+ }
72
+ } catch {
73
+ }
74
+ }
75
+ return null;
76
+ }
77
+ function parseAlphaToken(token) {
78
+ const trimmed = token.trim();
79
+ if (!trimmed) return null;
80
+ if (trimmed.endsWith("%")) {
81
+ const pct = Number.parseFloat(trimmed.slice(0, -1));
82
+ if (!Number.isFinite(pct)) return null;
83
+ return Math.max(0, Math.min(1, pct / 100));
84
+ }
85
+ const num = Number.parseFloat(trimmed);
86
+ if (!Number.isFinite(num)) return null;
87
+ const normalized = num > 1 ? num / 100 : num;
88
+ return Math.max(0, Math.min(1, normalized));
89
+ }
90
+ function extractColorAlpha(value) {
91
+ const raw = value.trim().toLowerCase();
92
+ if (!raw) return null;
93
+ const hex8 = raw.match(/^#[0-9a-f]{8}$/);
94
+ if (hex8) {
95
+ const alpha = Number.parseInt(raw.slice(7, 9), 16) / 255;
96
+ return Math.max(0, Math.min(1, alpha));
97
+ }
98
+ const legacyFunc = raw.match(/^(?:rgba|hsla)\(([^)]+)\)$/);
99
+ if (legacyFunc) {
100
+ const parts = legacyFunc[1].split(",").map((p) => p.trim());
101
+ if (parts.length >= 4) {
102
+ return parseAlphaToken(parts[3]);
103
+ }
104
+ }
105
+ const modernSlash = raw.match(/^(?:rgb|rgba|hsl|hsla)\((.+)\)$/);
106
+ if (modernSlash && modernSlash[1].includes("/")) {
107
+ const alphaPart = modernSlash[1].split("/")[1];
108
+ if (alphaPart) return parseAlphaToken(alphaPart);
109
+ }
110
+ return null;
111
+ }
112
+ function hexToRgb(hex) {
113
+ const cleaned = hex.replace("#", "");
114
+ return {
115
+ r: Number.parseInt(cleaned.slice(0, 2), 16),
116
+ g: Number.parseInt(cleaned.slice(2, 4), 16),
117
+ b: Number.parseInt(cleaned.slice(4, 6), 16)
118
+ };
119
+ }
120
+ function applyMappedColorPreservingAlpha(originalValue, mappedHex) {
121
+ if (mappedHex === "transparent" || mappedHex === "none") return "none";
122
+ const alpha = extractColorAlpha(originalValue);
123
+ if (alpha == null || alpha >= 0.999) return mappedHex;
124
+ const { r, g, b } = hexToRgb(mappedHex);
125
+ const alphaText = Number(alpha.toFixed(4)).toString();
126
+ return `rgba(${r}, ${g}, ${b}, ${alphaText})`;
127
+ }
128
+ function parseStyleDeclarations(styleText) {
129
+ return styleText.split(";").map((decl) => decl.trim()).filter(Boolean).map((decl) => {
130
+ const idx = decl.indexOf(":");
131
+ if (idx === -1) return null;
132
+ return {
133
+ key: decl.slice(0, idx).trim().toLowerCase(),
134
+ value: decl.slice(idx + 1).trim()
135
+ };
136
+ }).filter((x) => !!x);
137
+ }
138
+ const SHAPE_TAGS = /* @__PURE__ */ new Set([
139
+ "path",
140
+ "rect",
141
+ "circle",
142
+ "ellipse",
143
+ "line",
144
+ "polyline",
145
+ "polygon",
146
+ "text",
147
+ "tspan",
148
+ "g",
149
+ "use"
150
+ ]);
151
+ function hasImplicitBlackFill(el, styleBlocks) {
152
+ const tag = el.tagName.toLowerCase();
153
+ if (!SHAPE_TAGS.has(tag)) return false;
154
+ const fill = el.getAttribute("fill");
155
+ if (fill) return false;
156
+ const style = el.getAttribute("style");
157
+ if (style) {
158
+ const decls = parseStyleDeclarations(style);
159
+ const fillDecl = decls.find((d) => d.key === "fill");
160
+ if (fillDecl) return false;
161
+ }
162
+ const cls = el.getAttribute("class");
163
+ if (cls && styleBlocks) {
164
+ const classNames = cls.split(/\s+/);
165
+ for (const cn of classNames) {
166
+ if (styleBlocks.includes(`.${cn}`) && /fill\s*:/i.test(styleBlocks)) return false;
167
+ }
168
+ }
169
+ return true;
170
+ }
171
+ function hasImplicitBlackStopColor(el, styleBlocks) {
172
+ const tag = el.tagName.toLowerCase();
173
+ if (!(tag === "stop" || tag.endsWith(":stop"))) return false;
174
+ const hasAttrStopColor = !!(el.getAttribute("stop-color") || el.getAttribute("svg:stop-color"));
175
+ if (hasAttrStopColor) return false;
176
+ const style = el.getAttribute("style");
177
+ if (style) {
178
+ const hasInlineStopColor = parseStyleDeclarations(style).some(({ key }) => key === "stop-color" || key.endsWith(":stop-color"));
179
+ if (hasInlineStopColor) return false;
180
+ }
181
+ let hasClassStopColor = false;
182
+ collectFromClassRules(el, styleBlocks, (ruleBody) => {
183
+ if (hasClassStopColor) return;
184
+ hasClassStopColor = parseStyleDeclarations(ruleBody).some(({ key }) => key === "stop-color" || key.endsWith(":stop-color"));
185
+ });
186
+ return !hasClassStopColor;
187
+ }
188
+ function escapeRegexToken(value) {
189
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
190
+ }
191
+ function collectFromClassRules(el, styleBlockText, onRuleBody) {
192
+ if (!styleBlockText) return;
193
+ const classAttr = el.getAttribute("class");
194
+ if (!classAttr) return;
195
+ const classNames = classAttr.split(/\s+/).map((c) => c.trim()).filter(Boolean);
196
+ if (classNames.length === 0) return;
197
+ for (const className of classNames) {
198
+ const classRegex = new RegExp(`\\.${escapeRegexToken(className)}[^\\{]*\\{([^}]*)\\}`, "gi");
199
+ let match;
200
+ while ((match = classRegex.exec(styleBlockText)) !== null) {
201
+ onRuleBody(match[1] || "");
202
+ }
203
+ }
204
+ }
205
+ function isPaintStyleKey(key) {
206
+ return key === "fill" || key === "stroke" || key === "stop-color" || key.endsWith(":fill") || key.endsWith(":stroke") || key.endsWith(":stop-color");
207
+ }
208
+ function isStopColorStyleKey(key) {
209
+ return key === "stop-color" || key.endsWith(":stop-color");
210
+ }
211
+ function applySvgColorMap(svgText, colorMap) {
212
+ if (!svgText || !colorMap || Object.keys(colorMap).length === 0) return svgText;
213
+ const normalizedMap = /* @__PURE__ */ new Map();
214
+ const transparentTargets = /* @__PURE__ */ new Set();
215
+ for (const [from, to] of Object.entries(colorMap)) {
216
+ const key = normalizeSvgColor(from);
217
+ if (!key) continue;
218
+ const isTransparentTarget = to === "transparent" || to === "none" || to === "";
219
+ if (isTransparentTarget) {
220
+ normalizedMap.set(key, "transparent");
221
+ transparentTargets.add(key);
222
+ } else if (/^#[0-9a-f]{6}$/i.test(to)) {
223
+ normalizedMap.set(key, to.toLowerCase());
224
+ }
225
+ }
226
+ if (normalizedMap.size === 0) return svgText;
227
+ const mapEntries = Array.from(normalizedMap.entries()).filter(([k]) => !transparentTargets.has(k)).map(([k, v]) => ({ hex: k, rgb: hexToRgb(k), target: v }));
228
+ const fuzzyThresholdSq = 30 * 30;
229
+ function fuzzyLookup(value) {
230
+ const norm = normalizeSvgColor(value);
231
+ if (!norm) return null;
232
+ const exact = normalizedMap.get(norm);
233
+ if (exact) return exact;
234
+ const rgb = hexToRgb(norm);
235
+ let bestDist = Infinity;
236
+ let bestTarget = null;
237
+ for (const entry of mapEntries) {
238
+ const dr = rgb.r - entry.rgb.r;
239
+ const dg = rgb.g - entry.rgb.g;
240
+ const db = rgb.b - entry.rgb.b;
241
+ const distSq = dr * dr + dg * dg + db * db;
242
+ if (distSq < bestDist && distSq <= fuzzyThresholdSq) {
243
+ bestDist = distSq;
244
+ bestTarget = entry.target;
245
+ }
246
+ }
247
+ return bestTarget;
248
+ }
249
+ const implicitBlackTarget = normalizedMap.get("#000000");
250
+ const isTransparentMapTarget = (mapped) => mapped === "transparent" || mapped === "none";
251
+ const isStopColorProperty = (property) => property === "stop-color" || property === "svg:stop-color" || property.endsWith(":stop-color");
252
+ const TRANSPARENT_STOP_FALLBACK_COLOR = "#000000";
253
+ const toMappedPaint = (originalValue, mapped, property) => {
254
+ if (isTransparentMapTarget(mapped) && isStopColorProperty(property)) return TRANSPARENT_STOP_FALLBACK_COLOR;
255
+ return applyMappedColorPreservingAlpha(originalValue, mapped);
256
+ };
257
+ try {
258
+ if (typeof DOMParser !== "undefined" && typeof XMLSerializer !== "undefined") {
259
+ const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
260
+ const parserError = doc.querySelector("parsererror");
261
+ if (!parserError) {
262
+ let styleBlockText = "";
263
+ doc.querySelectorAll("style").forEach((s) => {
264
+ styleBlockText += s.textContent || "";
265
+ });
266
+ const elements = doc.querySelectorAll("*");
267
+ elements.forEach((el) => {
268
+ ["fill", "stroke", "stop-color", "svg:fill", "svg:stroke", "svg:stop-color"].forEach((attr) => {
269
+ const val = el.getAttribute(attr);
270
+ if (!val) return;
271
+ const mapped = fuzzyLookup(val);
272
+ if (!mapped) return;
273
+ const mappedValue = toMappedPaint(val, mapped, attr);
274
+ el.setAttribute(attr, mappedValue);
275
+ if (isStopColorProperty(attr) && isTransparentMapTarget(mapped)) {
276
+ el.setAttribute("stop-opacity", "0");
277
+ }
278
+ });
279
+ if (implicitBlackTarget && hasImplicitBlackFill(el, styleBlockText)) {
280
+ el.setAttribute("fill", applyMappedColorPreservingAlpha("#000000", implicitBlackTarget));
281
+ }
282
+ if (implicitBlackTarget && hasImplicitBlackStopColor(el, styleBlockText)) {
283
+ const isTransparentStop = isTransparentMapTarget(implicitBlackTarget);
284
+ el.setAttribute(
285
+ "stop-color",
286
+ isTransparentStop ? TRANSPARENT_STOP_FALLBACK_COLOR : applyMappedColorPreservingAlpha("#000000", implicitBlackTarget)
287
+ );
288
+ if (isTransparentStop) {
289
+ el.setAttribute("stop-opacity", "0");
290
+ }
291
+ }
292
+ const style = el.getAttribute("style");
293
+ if (style) {
294
+ let forceStopOpacityZero = false;
295
+ const declarations = parseStyleDeclarations(style).map(({ key, value }) => {
296
+ if (isPaintStyleKey(key)) {
297
+ const mapped = fuzzyLookup(value);
298
+ if (mapped) {
299
+ const mappedValue = toMappedPaint(value, mapped, key);
300
+ if (isStopColorStyleKey(key) && isTransparentMapTarget(mapped)) {
301
+ forceStopOpacityZero = true;
302
+ }
303
+ return `${key}: ${mappedValue}`;
304
+ }
305
+ }
306
+ return `${key}: ${value}`;
307
+ });
308
+ const nextDeclarations = forceStopOpacityZero ? declarations.filter((decl) => !/^stop-opacity\s*:/i.test(decl)) : declarations;
309
+ if (forceStopOpacityZero) {
310
+ nextDeclarations.push("stop-opacity: 0");
311
+ }
312
+ el.setAttribute("style", nextDeclarations.join("; "));
313
+ }
314
+ });
315
+ doc.querySelectorAll("style").forEach((styleNode) => {
316
+ const css = styleNode.textContent || "";
317
+ styleNode.textContent = css.replace(/(((?:svg:)?fill|(?:svg:)?stroke|(?:svg:)?stop-color)\s*:\s*)([^;{}]+)/gi, (_all, prefix, prop, value) => {
318
+ const valueText = String(value);
319
+ const mapped = fuzzyLookup(valueText);
320
+ if (!mapped) return `${prefix}${valueText}`;
321
+ const mappedValue = toMappedPaint(valueText, mapped, String(prop).toLowerCase());
322
+ if (isStopColorProperty(String(prop).toLowerCase()) && isTransparentMapTarget(mapped)) {
323
+ return `${prefix}${mappedValue}; stop-opacity: 0`;
324
+ }
325
+ return `${prefix}${mappedValue}`;
326
+ });
327
+ });
328
+ return new XMLSerializer().serializeToString(doc);
329
+ }
330
+ }
331
+ } catch {
332
+ }
333
+ let result = svgText;
334
+ result = result.replace(
335
+ /(((?:svg:)?fill|(?:svg:)?stroke|(?:svg:)?stop-color)\s*=\s*["'])([^"']+)(["'])/gi,
336
+ (full, prefix, prop, value, suffix) => {
337
+ const valueText = String(value);
338
+ const mapped = fuzzyLookup(valueText);
339
+ if (!mapped) return full;
340
+ const propText = String(prop).toLowerCase();
341
+ const mappedValue = toMappedPaint(valueText, mapped, propText);
342
+ return `${prefix}${mappedValue}${suffix}`;
343
+ }
344
+ );
345
+ result = result.replace(
346
+ /(((?:svg:)?fill|(?:svg:)?stroke|(?:svg:)?stop-color)\s*:\s*)([^;{}]+)/gi,
347
+ (full, prefix, prop, value) => {
348
+ const valueText = String(value);
349
+ const mapped = fuzzyLookup(valueText);
350
+ if (!mapped) return full;
351
+ const propText = String(prop).toLowerCase();
352
+ const mappedValue = toMappedPaint(valueText, mapped, propText);
353
+ if (isStopColorProperty(propText) && isTransparentMapTarget(mapped)) {
354
+ return `${prefix}${mappedValue}; stop-opacity: 0`;
355
+ }
356
+ return `${prefix}${mappedValue}`;
357
+ }
358
+ );
359
+ if (implicitBlackTarget) {
360
+ const implicitFillValue = applyMappedColorPreservingAlpha("#000000", implicitBlackTarget);
361
+ result = result.replace(
362
+ /(<(?:path|rect|circle|ellipse|polygon|polyline|line|text)\b)(?![^>]*\bfill\b)([^>]*>)/gi,
363
+ `$1 fill="${implicitFillValue}"$2`
364
+ );
365
+ result = result.replace(/<((?:svg:)?stop)\b([^>]*)>/gi, (full, tag, attrs) => {
366
+ const attrsText = String(attrs || "");
367
+ if (/\b(?:svg:)?stop-color\s*=\s*["'][^"']*["']/i.test(attrsText)) return full;
368
+ if (/\bstyle\s*=\s*["'][^"']*(?:svg:)?stop-color\s*:[^"']*["']/i.test(attrsText)) return full;
369
+ const isTransparentStop = isTransparentMapTarget(implicitBlackTarget);
370
+ const stopColorValue = isTransparentStop ? TRANSPARENT_STOP_FALLBACK_COLOR : applyMappedColorPreservingAlpha("#000000", implicitBlackTarget);
371
+ const stopOpacityAttr = isTransparentStop ? ' stop-opacity="0"' : "";
372
+ return `<${tag}${attrsText} stop-color="${stopColorValue}"${stopOpacityAttr}>`;
373
+ });
374
+ }
375
+ return result;
376
+ }
377
+ exports.applySvgColorMap = applySvgColorMap;
378
+ exports.normalizeSvgColor = normalizeSvgColor;
379
+ //# sourceMappingURL=svgColorUtils-DQN6fbIM.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svgColorUtils-DQN6fbIM.cjs","sources":["../../../src/lib/svgColorUtils.ts"],"sourcesContent":["/**\n * SVG Color Extraction & Replacement Utilities\n */\n\nconst NON_RECOLORABLE = new Set([\n 'none',\n 'transparent',\n 'currentcolor',\n 'inherit',\n 'initial',\n 'unset',\n]);\n\nlet colorCtx: CanvasRenderingContext2D | null | undefined;\n\nfunction getColorContext(): CanvasRenderingContext2D | null {\n if (colorCtx !== undefined) return colorCtx;\n if (typeof document === 'undefined') {\n colorCtx = null;\n return colorCtx;\n }\n colorCtx = document.createElement('canvas').getContext('2d');\n return colorCtx;\n}\n\n/** Normalize supported CSS color values to #rrggbb. Returns null for non-recolorable values. */\nexport function normalizeSvgColor(value: string): string | null {\n if (!value) return null;\n const raw = value.trim().toLowerCase();\n if (!raw || NON_RECOLORABLE.has(raw) || raw.startsWith('url(') || raw.startsWith('var(')) return null;\n\n if (/^#[0-9a-f]{3}$/.test(raw)) {\n return `#${raw[1]}${raw[1]}${raw[2]}${raw[2]}${raw[3]}${raw[3]}`;\n }\n if (/^#[0-9a-f]{6}$/.test(raw)) return raw;\n if (/^#[0-9a-f]{8}$/.test(raw)) return raw.slice(0, 7);\n\n const rgb = raw.match(/^rgba?\\(([^)]+)\\)$/);\n if (rgb) {\n const body = rgb[1].trim();\n const colorPortion = body.includes('/') ? body.split('/')[0].trim() : body;\n\n const commaParts = colorPortion.split(',').map((p) => p.trim()).filter(Boolean);\n const parts = commaParts.length >= 3 ? commaParts : colorPortion.split(/\\s+/).map((p) => p.trim()).filter(Boolean);\n\n if (parts.length >= 3) {\n const parseRgbComponent = (token: string): number | null => {\n const t = token.trim();\n if (!t) return null;\n if (t.endsWith('%')) {\n const pct = Number.parseFloat(t.slice(0, -1));\n if (!Number.isFinite(pct)) return null;\n return Math.max(0, Math.min(255, Math.round((pct / 100) * 255)));\n }\n const num = Number.parseFloat(t);\n if (!Number.isFinite(num)) return null;\n return Math.max(0, Math.min(255, Math.round(num)));\n };\n\n const rgbNums = parts.slice(0, 3).map(parseRgbComponent);\n if (rgbNums.every((n): n is number => n != null)) {\n const [r, g, b] = rgbNums;\n return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;\n }\n }\n }\n\n // Browser-normalized fallback for named/hsl/etc CSS colors\n const ctx = getColorContext();\n if (ctx) {\n try {\n ctx.fillStyle = '#000000';\n ctx.fillStyle = raw;\n const parsed = String(ctx.fillStyle).toLowerCase();\n if (/^#[0-9a-f]{6}$/.test(parsed)) return parsed;\n const parsedRgb = parsed.match(/^rgba?\\(([^)]+)\\)$/);\n if (parsedRgb) {\n const parts = parsedRgb[1].split(',').map((p) => p.trim());\n const nums = parts.slice(0, 3).map((p) => Number.parseFloat(p));\n if (nums.every((n) => Number.isFinite(n))) {\n const [r, g, b] = nums.map((n) => Math.max(0, Math.min(255, Math.round(n))));\n return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;\n }\n }\n } catch {\n // ignore invalid css colors\n }\n }\n\n return null;\n}\n\nfunction parseAlphaToken(token: string): number | null {\n const trimmed = token.trim();\n if (!trimmed) return null;\n\n if (trimmed.endsWith('%')) {\n const pct = Number.parseFloat(trimmed.slice(0, -1));\n if (!Number.isFinite(pct)) return null;\n return Math.max(0, Math.min(1, pct / 100));\n }\n\n const num = Number.parseFloat(trimmed);\n if (!Number.isFinite(num)) return null;\n const normalized = num > 1 ? num / 100 : num;\n return Math.max(0, Math.min(1, normalized));\n}\n\n/** Extract alpha channel from a CSS color literal, if present. */\nfunction extractColorAlpha(value: string): number | null {\n const raw = value.trim().toLowerCase();\n if (!raw) return null;\n\n const hex8 = raw.match(/^#[0-9a-f]{8}$/);\n if (hex8) {\n const alpha = Number.parseInt(raw.slice(7, 9), 16) / 255;\n return Math.max(0, Math.min(1, alpha));\n }\n\n // rgba(255,0,0,0.5) and hsla(0,100%,50%,0.5)\n const legacyFunc = raw.match(/^(?:rgba|hsla)\\(([^)]+)\\)$/);\n if (legacyFunc) {\n const parts = legacyFunc[1].split(',').map((p) => p.trim());\n if (parts.length >= 4) {\n return parseAlphaToken(parts[3]);\n }\n }\n\n // rgb(255 0 0 / 50%) modern syntax\n const modernSlash = raw.match(/^(?:rgb|rgba|hsl|hsla)\\((.+)\\)$/);\n if (modernSlash && modernSlash[1].includes('/')) {\n const alphaPart = modernSlash[1].split('/')[1];\n if (alphaPart) return parseAlphaToken(alphaPart);\n }\n\n return null;\n}\n\nfunction hexToRgb(hex: string): { r: number; g: number; b: number } {\n const cleaned = hex.replace('#', '');\n return {\n r: Number.parseInt(cleaned.slice(0, 2), 16),\n g: Number.parseInt(cleaned.slice(2, 4), 16),\n b: Number.parseInt(cleaned.slice(4, 6), 16),\n };\n}\n\n/** Keep source alpha (if any) while swapping hue to mapped color. */\nfunction applyMappedColorPreservingAlpha(originalValue: string, mappedHex: string): string {\n // Transparent / none → use SVG-standard 'none' for proper rendering in svg2pdf and browsers.\n if (mappedHex === 'transparent' || mappedHex === 'none') return 'none';\n const alpha = extractColorAlpha(originalValue);\n if (alpha == null || alpha >= 0.999) return mappedHex;\n const { r, g, b } = hexToRgb(mappedHex);\n const alphaText = Number(alpha.toFixed(4)).toString();\n return `rgba(${r}, ${g}, ${b}, ${alphaText})`;\n}\n\nfunction parseStyleDeclarations(styleText: string): Array<{ key: string; value: string }> {\n return styleText\n .split(';')\n .map((decl) => decl.trim())\n .filter(Boolean)\n .map((decl) => {\n const idx = decl.indexOf(':');\n if (idx === -1) return null;\n return {\n key: decl.slice(0, idx).trim().toLowerCase(),\n value: decl.slice(idx + 1).trim(),\n };\n })\n .filter((x): x is { key: string; value: string } => !!x);\n}\n\nconst SHAPE_TAGS = new Set([\n 'path', 'rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'text', 'tspan', 'g', 'use',\n]);\n\nconst NON_DRAWABLE_TAGS = new Set(['defs', 'metadata', 'style', 'title', 'desc', 'script']);\n\ninterface BoundsRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** Check if an element has an implicit black fill (no fill attribute and not set to none). */\nfunction hasImplicitBlackFill(el: Element, styleBlocks: string): boolean {\n const tag = el.tagName.toLowerCase();\n if (!SHAPE_TAGS.has(tag)) return false;\n const fill = el.getAttribute('fill');\n if (fill) return false;\n const style = el.getAttribute('style');\n if (style) {\n const decls = parseStyleDeclarations(style);\n const fillDecl = decls.find((d) => d.key === 'fill');\n if (fillDecl) return false;\n }\n const cls = el.getAttribute('class');\n if (cls && styleBlocks) {\n const classNames = cls.split(/\\s+/);\n for (const cn of classNames) {\n if (styleBlocks.includes(`.${cn}`) && /fill\\s*:/i.test(styleBlocks)) return false;\n }\n }\n return true;\n}\n\n/**\n * SVG stop-color defaults to black when omitted.\n * Treat such stops as implicit black so gradient path colors are editable.\n */\nfunction hasImplicitBlackStopColor(el: Element, styleBlocks: string): boolean {\n const tag = el.tagName.toLowerCase();\n if (!(tag === 'stop' || tag.endsWith(':stop'))) return false;\n\n const hasAttrStopColor = !!(el.getAttribute('stop-color') || el.getAttribute('svg:stop-color'));\n if (hasAttrStopColor) return false;\n\n const style = el.getAttribute('style');\n if (style) {\n const hasInlineStopColor = parseStyleDeclarations(style).some(({ key }) => key === 'stop-color' || key.endsWith(':stop-color'));\n if (hasInlineStopColor) return false;\n }\n\n let hasClassStopColor = false;\n collectFromClassRules(el, styleBlocks, (ruleBody) => {\n if (hasClassStopColor) return;\n hasClassStopColor = parseStyleDeclarations(ruleBody).some(({ key }) => key === 'stop-color' || key.endsWith(':stop-color'));\n });\n\n return !hasClassStopColor;\n}\n\nfunction parseSvgLengthNumber(value: string | null): number | null {\n if (!value) return null;\n const parsed = Number.parseFloat(value.replace(/px$/i, '').trim());\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nfunction parseViewBox(svgRoot: SVGSVGElement): BoundsRect | null {\n const viewBox = svgRoot.getAttribute('viewBox');\n if (viewBox) {\n const values = viewBox.split(/[\\s,]+/).map((n) => Number.parseFloat(n)).filter((n) => Number.isFinite(n));\n if (values.length === 4 && values[2] > 0 && values[3] > 0) {\n return { x: values[0], y: values[1], width: values[2], height: values[3] };\n }\n }\n\n const width = parseSvgLengthNumber(svgRoot.getAttribute('width'));\n const height = parseSvgLengthNumber(svgRoot.getAttribute('height'));\n if (width && height) {\n return { x: 0, y: 0, width, height };\n }\n\n return null;\n}\n\nfunction collectReferencedIdsFromString(value: string | null, ids: Set<string>) {\n if (!value) return;\n const normalizedValue = value.replace(/&quot;/gi, '\"').replace(/&apos;/gi, \"'\");\n // Support url(#id), url(\"#id\"), url('#id') and optional whitespace.\n const urlRegex = /url\\(\\s*['\"]?#([^)\"'\\s]+)['\"]?\\s*\\)/gi;\n let match: RegExpExecArray | null;\n while ((match = urlRegex.exec(normalizedValue)) !== null) {\n if (match[1]) ids.add(match[1]);\n }\n}\n\nfunction escapeRegexToken(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction collectFromClassRules(el: Element, styleBlockText: string, onRuleBody: (ruleBody: string) => void) {\n if (!styleBlockText) return;\n const classAttr = el.getAttribute('class');\n if (!classAttr) return;\n\n const classNames = classAttr.split(/\\s+/).map((c) => c.trim()).filter(Boolean);\n if (classNames.length === 0) return;\n\n for (const className of classNames) {\n const classRegex = new RegExp(`\\\\.${escapeRegexToken(className)}[^\\\\{]*\\\\{([^}]*)\\\\}`, 'gi');\n let match: RegExpExecArray | null;\n while ((match = classRegex.exec(styleBlockText)) !== null) {\n onRuleBody(match[1] || '');\n }\n }\n}\n\nfunction collectReferencedDefIds(el: Element, ids: Set<string>, styleBlockText: string = '') {\n const attrs = ['fill', 'stroke', 'clip-path', 'mask', 'filter', 'style', 'marker-start', 'marker-mid', 'marker-end'];\n for (const attr of attrs) {\n collectReferencedIdsFromString(el.getAttribute(attr), ids);\n }\n\n collectFromClassRules(el, styleBlockText, (ruleBody) => {\n collectReferencedIdsFromString(ruleBody, ids);\n });\n\n const href = el.getAttribute('href') || el.getAttribute('xlink:href');\n if (href?.startsWith('#')) ids.add(href.slice(1));\n\n try {\n if (typeof window !== 'undefined') {\n const computed = window.getComputedStyle(el);\n collectReferencedIdsFromString(computed.fill, ids);\n collectReferencedIdsFromString(computed.stroke, ids);\n collectReferencedIdsFromString(computed.clipPath, ids);\n collectReferencedIdsFromString(computed.mask, ids);\n collectReferencedIdsFromString(computed.filter, ids);\n }\n } catch {\n // Ignore computed style errors\n }\n}\n\nfunction getScreenBounds(el: SVGGraphicsElement): BoundsRect | null {\n try {\n const bbox = el.getBBox();\n if (bbox.width <= 0 && bbox.height <= 0) return null;\n\n const matrix = el.getScreenCTM();\n if (!matrix) {\n return { x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height };\n }\n\n const p1 = new DOMPoint(bbox.x, bbox.y).matrixTransform(matrix);\n const p2 = new DOMPoint(bbox.x + bbox.width, bbox.y).matrixTransform(matrix);\n const p3 = new DOMPoint(bbox.x, bbox.y + bbox.height).matrixTransform(matrix);\n const p4 = new DOMPoint(bbox.x + bbox.width, bbox.y + bbox.height).matrixTransform(matrix);\n\n const minX = Math.min(p1.x, p2.x, p3.x, p4.x);\n const minY = Math.min(p1.y, p2.y, p3.y, p4.y);\n const maxX = Math.max(p1.x, p2.x, p3.x, p4.x);\n const maxY = Math.max(p1.y, p2.y, p3.y, p4.y);\n\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n } catch {\n return null;\n }\n}\n\nfunction getViewBoxScreenBounds(svgRoot: SVGSVGElement, viewBox: BoundsRect): BoundsRect | null {\n const matrix = svgRoot.getScreenCTM();\n if (!matrix || typeof DOMPoint === 'undefined') return null;\n\n const p1 = new DOMPoint(viewBox.x, viewBox.y).matrixTransform(matrix);\n const p2 = new DOMPoint(viewBox.x + viewBox.width, viewBox.y + viewBox.height).matrixTransform(matrix);\n\n const minX = Math.min(p1.x, p2.x);\n const minY = Math.min(p1.y, p2.y);\n const maxX = Math.max(p1.x, p2.x);\n const maxY = Math.max(p1.y, p2.y);\n\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\nfunction intersects(a: BoundsRect, b: BoundsRect): boolean {\n return !(a.x > b.x + b.width || a.x + a.width < b.x || a.y > b.y + b.height || a.y + a.height < b.y);\n}\n\nfunction addRecolorableColor(value: string | null, colors: Set<string>) {\n if (!value) return;\n const normalized = normalizeSvgColor(value);\n if (normalized) colors.add(normalized);\n}\n\nfunction isPaintStyleKey(key: string): boolean {\n return key === 'fill' || key === 'stroke' || key === 'stop-color' || key.endsWith(':fill') || key.endsWith(':stroke') || key.endsWith(':stop-color');\n}\n\nfunction isStopColorStyleKey(key: string): boolean {\n return key === 'stop-color' || key.endsWith(':stop-color');\n}\n\nfunction collectPaintColorsFromClassRules(el: Element, styleBlockText: string, colors: Set<string>) {\n collectFromClassRules(el, styleBlockText, (ruleBody) => {\n parseStyleDeclarations(ruleBody).forEach(({ key, value }) => {\n if (isPaintStyleKey(key)) {\n addRecolorableColor(value, colors);\n }\n });\n });\n}\n\nfunction extractElementPaintColors(el: Element, colors: Set<string>, styleBlockText = '') {\n addRecolorableColor(el.getAttribute('fill'), colors);\n addRecolorableColor(el.getAttribute('stroke'), colors);\n addRecolorableColor(el.getAttribute('stop-color'), colors);\n\n const style = el.getAttribute('style');\n if (style) {\n parseStyleDeclarations(style).forEach(({ key, value }) => {\n if (isPaintStyleKey(key)) {\n addRecolorableColor(value, colors);\n }\n });\n }\n\n try {\n if (typeof window !== 'undefined') {\n const computed = window.getComputedStyle(el);\n addRecolorableColor(computed.fill, colors);\n addRecolorableColor(computed.stroke, colors);\n addRecolorableColor(computed.stopColor, colors);\n }\n } catch {\n // Ignore computed style errors\n }\n\n if (hasImplicitBlackStopColor(el, styleBlockText)) {\n colors.add('#000000');\n }\n}\n\nfunction normalizeSvgMarkupForColorExtraction(svgText: string): string {\n if (!svgText) return svgText;\n return svgText\n .trim()\n .replace(/^\\uFEFF/, '')\n .replace(/<(\\/?)\\s*svg:svg\\b/gi, '<$1svg')\n .replace(/<(\\/?)\\s*svg:/gi, '<$1')\n .replace(/(\\s)svg:([a-zA-Z_][\\w:.-]*)(\\s*=)/gi, '$1$2$3')\n .replace(/\\s+xmlns:svg\\s*=\\s*\"[^\"]*\"/gi, '')\n .replace(/\\s+xmlns:svg\\s*=\\s*'[^']*'/gi, '');\n}\n\nfunction collectColorLiteralsFromMarkup(svgText: string, colors: Set<string>) {\n if (!svgText) return;\n const literalRegex = /#[0-9a-fA-F]{3,8}\\b|(?:rgba?|hsla?)\\([^)]*\\)/g;\n let match: RegExpExecArray | null;\n while ((match = literalRegex.exec(svgText)) !== null) {\n const normalized = normalizeSvgColor(match[0]);\n if (normalized) colors.add(normalized);\n }\n}\n\nfunction collectStopColorsFromMarkup(svgText: string, colors: Set<string>) {\n if (!svgText) return;\n\n const attrRegex = /(?:svg:)?stop-color\\s*=\\s*[\"']([^\"']+)[\"']/gi;\n let match: RegExpExecArray | null;\n while ((match = attrRegex.exec(svgText)) !== null) {\n addRecolorableColor((match[1] || '').trim(), colors);\n }\n\n const styleRegex = /(?:^|[;\\s{])(?:svg:)?stop-color\\s*:\\s*([^;{}]+)/gi;\n while ((match = styleRegex.exec(svgText)) !== null) {\n addRecolorableColor((match[1] || '').trim(), colors);\n }\n}\n\n/** Extract all unique recolorable fill/stroke colors from SVG. */\nexport function extractSvgColors(svgText: string): string[] {\n if (!svgText) return [];\n\n const normalizedSvgText = normalizeSvgMarkupForColorExtraction(svgText);\n const colors = new Set<string>();\n let hasImplicitBlack = false;\n\n try {\n if (typeof DOMParser !== 'undefined' && typeof document !== 'undefined') {\n const doc = new DOMParser().parseFromString(normalizedSvgText, 'image/svg+xml');\n const parserError = doc.querySelector('parsererror');\n if (!parserError) {\n const sourceRoot = doc.documentElement;\n if (!sourceRoot || sourceRoot.tagName.toLowerCase() !== 'svg') return [];\n\n const importedRoot = document.importNode(sourceRoot, true);\n if (!(importedRoot instanceof SVGSVGElement)) return [];\n const svgRoot = importedRoot;\n\n let styleBlockText = '';\n svgRoot.querySelectorAll('style').forEach((s) => {\n styleBlockText += s.textContent || '';\n });\n\n svgRoot.style.position = 'absolute';\n svgRoot.style.left = '-99999px';\n svgRoot.style.top = '-99999px';\n svgRoot.style.visibility = 'hidden';\n svgRoot.style.pointerEvents = 'none';\n\n document.body.appendChild(svgRoot);\n\n try {\n const viewBox = parseViewBox(svgRoot);\n const viewBounds = viewBox ? getViewBoxScreenBounds(svgRoot, viewBox) : null;\n\n const usedDefIds = new Set<string>();\n const allElements = Array.from(svgRoot.querySelectorAll('*'));\n\n for (const el of allElements) {\n const tag = el.tagName.toLowerCase();\n // First pass should only inspect visible scene content, not defs.\n if (NON_DRAWABLE_TAGS.has(tag) || tag === 'stop' || !!el.closest('defs')) continue;\n\n let intersectsView = true;\n if (viewBounds && el instanceof SVGGraphicsElement) {\n const elBounds = getScreenBounds(el);\n if (elBounds) intersectsView = intersects(elBounds, viewBounds);\n }\n if (!intersectsView) continue;\n\n extractElementPaintColors(el, colors, styleBlockText);\n collectPaintColorsFromClassRules(el, styleBlockText, colors);\n collectReferencedDefIds(el, usedDefIds, styleBlockText);\n\n if (!hasImplicitBlack && hasImplicitBlackFill(el, styleBlockText)) {\n hasImplicitBlack = true;\n }\n }\n\n const idMap = new Map<string, Element>();\n svgRoot.querySelectorAll('[id]').forEach((node) => {\n const id = node.getAttribute('id');\n if (id) idMap.set(id, node);\n });\n\n const resolvedDefIds = new Set<string>();\n const queue = Array.from(usedDefIds);\n\n while (queue.length > 0) {\n const id = queue.pop()!;\n if (resolvedDefIds.has(id)) continue;\n resolvedDefIds.add(id);\n\n const defNode = idMap.get(id);\n if (!defNode) continue;\n\n collectReferencedDefIds(defNode, usedDefIds, styleBlockText);\n for (const nestedId of usedDefIds) {\n if (!resolvedDefIds.has(nestedId)) queue.push(nestedId);\n }\n }\n\n for (const id of resolvedDefIds) {\n const defNode = idMap.get(id);\n if (!defNode) continue;\n\n extractElementPaintColors(defNode, colors, styleBlockText);\n collectPaintColorsFromClassRules(defNode, styleBlockText, colors);\n defNode.querySelectorAll('*').forEach((node) => {\n extractElementPaintColors(node, colors, styleBlockText);\n collectPaintColorsFromClassRules(node, styleBlockText, colors);\n });\n }\n\n // Keep extraction strict: visible shapes + defs they actually reference.\n // Only broaden to literal scanning if strict extraction found nothing.\n if (colors.size === 0) {\n collectStopColorsFromMarkup(styleBlockText, colors);\n collectColorLiteralsFromMarkup(normalizedSvgText, colors);\n collectStopColorsFromMarkup(normalizedSvgText, colors);\n }\n } finally {\n document.body.removeChild(svgRoot);\n }\n\n if (hasImplicitBlack) {\n colors.add('#000000');\n }\n\n return Array.from(colors);\n }\n }\n } catch {\n // If strict extraction partially succeeded, prefer that over broad literal fallback.\n if (colors.size > 0) return Array.from(colors);\n // fallback below\n }\n\n // Regex fallback\n const attrRegex = /(?:(?:svg:)?fill|(?:svg:)?stroke|(?:svg:)?stop-color)\\s*=\\s*[\"']([^\"']+)[\"']/gi;\n let match: RegExpExecArray | null;\n while ((match = attrRegex.exec(normalizedSvgText))) {\n const norm = normalizeSvgColor(match[1]);\n if (norm) colors.add(norm);\n }\n\n const styleRegex = /(?:(?:svg:)?fill|(?:svg:)?stroke|(?:svg:)?stop-color)\\s*:\\s*([^;{}]+)/gi;\n while ((match = styleRegex.exec(normalizedSvgText))) {\n const norm = normalizeSvgColor(match[1]);\n if (norm) colors.add(norm);\n }\n\n collectColorLiteralsFromMarkup(normalizedSvgText, colors);\n collectStopColorsFromMarkup(normalizedSvgText, colors);\n\n if (colors.size === 0 && /<(?:path|rect|circle|ellipse|polygon|polyline|line|text)\\b/i.test(normalizedSvgText)) {\n colors.add('#000000');\n }\n\n return Array.from(colors);\n}\n\n/** Apply a color replacement map to SVG fill/stroke attrs + inline/style-block declarations.\n * Handles implicit black: if colorMap maps #000000 → X, elements with no fill get fill=\"X\". */\nexport function applySvgColorMap(svgText: string, colorMap: Record<string, string>): string {\n if (!svgText || !colorMap || Object.keys(colorMap).length === 0) return svgText;\n\n const normalizedMap = new Map<string, string>();\n const transparentTargets = new Set<string>(); // keys whose target is transparent/none\n for (const [from, to] of Object.entries(colorMap)) {\n const key = normalizeSvgColor(from);\n if (!key) continue;\n const isTransparentTarget = to === 'transparent' || to === 'none' || to === '';\n if (isTransparentTarget) {\n normalizedMap.set(key, 'transparent');\n transparentTargets.add(key);\n } else if (/^#[0-9a-f]{6}$/i.test(to)) {\n normalizedMap.set(key, to.toLowerCase());\n }\n }\n if (normalizedMap.size === 0) return svgText;\n\n // Build a fuzzy lookup: for any color value, find the closest mapping key within threshold\n // Only build fuzzy entries for non-transparent targets (transparent is exact-match only)\n const mapEntries = Array.from(normalizedMap.entries())\n .filter(([k]) => !transparentTargets.has(k))\n .map(([k, v]) => ({ hex: k, rgb: hexToRgb(k), target: v }));\n const fuzzyThresholdSq = 30 * 30;\n\n function fuzzyLookup(value: string): string | null {\n const norm = normalizeSvgColor(value);\n if (!norm) return null;\n // Exact match first\n const exact = normalizedMap.get(norm);\n if (exact) return exact;\n // Fuzzy match\n const rgb = hexToRgb(norm);\n let bestDist = Infinity;\n let bestTarget: string | null = null;\n for (const entry of mapEntries) {\n const dr = rgb.r - entry.rgb.r;\n const dg = rgb.g - entry.rgb.g;\n const db = rgb.b - entry.rgb.b;\n const distSq = dr * dr + dg * dg + db * db;\n if (distSq < bestDist && distSq <= fuzzyThresholdSq) {\n bestDist = distSq;\n bestTarget = entry.target;\n }\n }\n return bestTarget;\n }\n\n const implicitBlackTarget = normalizedMap.get('#000000');\n\n const isTransparentMapTarget = (mapped: string): boolean =>\n mapped === 'transparent' || mapped === 'none';\n\n const isStopColorProperty = (property: string): boolean =>\n property === 'stop-color' || property === 'svg:stop-color' || property.endsWith(':stop-color');\n\n const TRANSPARENT_STOP_FALLBACK_COLOR = '#000000';\n\n const toMappedPaint = (originalValue: string, mapped: string, property: string): string => {\n if (isTransparentMapTarget(mapped) && isStopColorProperty(property)) return TRANSPARENT_STOP_FALLBACK_COLOR;\n return applyMappedColorPreservingAlpha(originalValue, mapped);\n };\n\n try {\n if (typeof DOMParser !== 'undefined' && typeof XMLSerializer !== 'undefined') {\n const doc = new DOMParser().parseFromString(svgText, 'image/svg+xml');\n const parserError = doc.querySelector('parsererror');\n if (!parserError) {\n // Collect style block text for class-based detection\n let styleBlockText = '';\n doc.querySelectorAll('style').forEach((s) => { styleBlockText += s.textContent || ''; });\n\n const elements = doc.querySelectorAll('*');\n elements.forEach((el) => {\n (['fill', 'stroke', 'stop-color', 'svg:fill', 'svg:stroke', 'svg:stop-color'] as const).forEach((attr) => {\n const val = el.getAttribute(attr);\n if (!val) return;\n const mapped = fuzzyLookup(val);\n if (!mapped) return;\n\n const mappedValue = toMappedPaint(val, mapped, attr);\n el.setAttribute(attr, mappedValue);\n\n if (isStopColorProperty(attr) && isTransparentMapTarget(mapped)) {\n el.setAttribute('stop-opacity', '0');\n }\n });\n\n // Handle implicit black: add explicit paint to elements that rely on SVG defaults.\n if (implicitBlackTarget && hasImplicitBlackFill(el, styleBlockText)) {\n el.setAttribute('fill', applyMappedColorPreservingAlpha('#000000', implicitBlackTarget));\n }\n if (implicitBlackTarget && hasImplicitBlackStopColor(el, styleBlockText)) {\n const isTransparentStop = isTransparentMapTarget(implicitBlackTarget);\n el.setAttribute(\n 'stop-color',\n isTransparentStop\n ? TRANSPARENT_STOP_FALLBACK_COLOR\n : applyMappedColorPreservingAlpha('#000000', implicitBlackTarget)\n );\n if (isTransparentStop) {\n el.setAttribute('stop-opacity', '0');\n }\n }\n\n const style = el.getAttribute('style');\n if (style) {\n let forceStopOpacityZero = false;\n const declarations = parseStyleDeclarations(style).map(({ key, value }) => {\n if (isPaintStyleKey(key)) {\n const mapped = fuzzyLookup(value);\n if (mapped) {\n const mappedValue = toMappedPaint(value, mapped, key);\n if (isStopColorStyleKey(key) && isTransparentMapTarget(mapped)) {\n forceStopOpacityZero = true;\n }\n return `${key}: ${mappedValue}`;\n }\n }\n return `${key}: ${value}`;\n });\n\n const nextDeclarations = forceStopOpacityZero\n ? declarations.filter((decl) => !/^stop-opacity\\s*:/i.test(decl))\n : declarations;\n\n if (forceStopOpacityZero) {\n nextDeclarations.push('stop-opacity: 0');\n }\n\n el.setAttribute('style', nextDeclarations.join('; '));\n }\n });\n\n doc.querySelectorAll('style').forEach((styleNode) => {\n const css = styleNode.textContent || '';\n styleNode.textContent = css.replace(/(((?:svg:)?fill|(?:svg:)?stroke|(?:svg:)?stop-color)\\s*:\\s*)([^;{}]+)/gi, (_all, prefix, prop, value) => {\n const valueText = String(value);\n const mapped = fuzzyLookup(valueText);\n if (!mapped) return `${prefix}${valueText}`;\n const mappedValue = toMappedPaint(valueText, mapped, String(prop).toLowerCase());\n if (isStopColorProperty(String(prop).toLowerCase()) && isTransparentMapTarget(mapped)) {\n return `${prefix}${mappedValue}; stop-opacity: 0`;\n }\n return `${prefix}${mappedValue}`;\n });\n });\n\n return new XMLSerializer().serializeToString(doc);\n }\n }\n } catch {\n // fallback below\n }\n\n // Regex fallback\n let result = svgText;\n result = result.replace(\n /(((?:svg:)?fill|(?:svg:)?stroke|(?:svg:)?stop-color)\\s*=\\s*[\"'])([^\"']+)([\"'])/gi,\n (full, prefix, prop, value, suffix) => {\n const valueText = String(value);\n const mapped = fuzzyLookup(valueText);\n if (!mapped) return full;\n const propText = String(prop).toLowerCase();\n const mappedValue = toMappedPaint(valueText, mapped, propText);\n return `${prefix}${mappedValue}${suffix}`;\n }\n );\n\n result = result.replace(\n /(((?:svg:)?fill|(?:svg:)?stroke|(?:svg:)?stop-color)\\s*:\\s*)([^;{}]+)/gi,\n (full, prefix, prop, value) => {\n const valueText = String(value);\n const mapped = fuzzyLookup(valueText);\n if (!mapped) return full;\n const propText = String(prop).toLowerCase();\n const mappedValue = toMappedPaint(valueText, mapped, propText);\n if (isStopColorProperty(propText) && isTransparentMapTarget(mapped)) {\n return `${prefix}${mappedValue}; stop-opacity: 0`;\n }\n return `${prefix}${mappedValue}`;\n }\n );\n\n // Regex fallback for implicit black defaults.\n if (implicitBlackTarget) {\n const implicitFillValue = applyMappedColorPreservingAlpha('#000000', implicitBlackTarget);\n result = result.replace(\n /(<(?:path|rect|circle|ellipse|polygon|polyline|line|text)\\b)(?![^>]*\\bfill\\b)([^>]*>)/gi,\n `$1 fill=\"${implicitFillValue}\"$2`\n );\n\n result = result.replace(/<((?:svg:)?stop)\\b([^>]*)>/gi, (full, tag, attrs) => {\n const attrsText = String(attrs || '');\n if (/\\b(?:svg:)?stop-color\\s*=\\s*[\"'][^\"']*[\"']/i.test(attrsText)) return full;\n if (/\\bstyle\\s*=\\s*[\"'][^\"']*(?:svg:)?stop-color\\s*:[^\"']*[\"']/i.test(attrsText)) return full;\n const isTransparentStop = isTransparentMapTarget(implicitBlackTarget);\n const stopColorValue = isTransparentStop\n ? TRANSPARENT_STOP_FALLBACK_COLOR\n : applyMappedColorPreservingAlpha('#000000', implicitBlackTarget);\n const stopOpacityAttr = isTransparentStop ? ' stop-opacity=\"0\"' : '';\n return `<${tag}${attrsText} stop-color=\"${stopColorValue}\"${stopOpacityAttr}>`;\n });\n }\n\n return result;\n}\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@pixldocs/canvas-renderer",
3
+ "version": "0.3.3",
4
+ "description": "Client-side template renderer for Pixldocs — React component + imperative API for rendering templates in any web app",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "vite build",
21
+ "dev": "vite build --watch"
22
+ },
23
+ "peerDependencies": {
24
+ "fabric": "^6.0.0",
25
+ "react": "^18.0.0 || ^19.0.0",
26
+ "react-dom": "^18.0.0 || ^19.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^25.6.0",
30
+ "@types/react": "^18.3.23",
31
+ "@vitejs/plugin-react-swc": "^3.11.0",
32
+ "fabric": "^6.9.1",
33
+ "react": "^18.3.1",
34
+ "react-dom": "^18.3.1",
35
+ "typescript": "^5.8.3",
36
+ "vite": "^5.4.19",
37
+ "vite-plugin-dts": "^4.5.0"
38
+ },
39
+ "license": "UNLICENSED",
40
+ "private": false
41
+ }