Repository navigation
Expand file tree
/
Copy pathiconout.js
More file actions
78 lines (74 loc) · 3.49 KB
/
Copy pathiconout.js
File metadata and controls
78 lines (74 loc) · 3.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/* Shared export pipeline — used by the main window (app.js) and quick search (quick.js). */
function exportSVG(svg, color) {
return svg.replaceAll('currentColor', color);
}
/* Wrap an icon SVG with padding and an optional background shape.
style = { pad: 0..0.35 (fraction), bg: 'none'|'circle'|'rounded'|'square', bgColor: '#hex' } */
function composeStyled(svg, style) {
const pad = Math.max(0, Math.min(0.35, style?.pad || 0));
const bg = style?.bg || 'none';
if (bg === 'none' && pad === 0) return svg;
const S = 512;
const inner = Math.round(S * (1 - 2 * pad));
const off = Math.round((S - inner) / 2);
let shape = '';
const c = style?.bgColor || '#FFFFFF';
if (bg === 'circle') shape = `<circle cx="${S / 2}" cy="${S / 2}" r="${S / 2}" fill="${c}"/>`;
else if (bg === 'rounded') shape = `<rect width="${S}" height="${S}" rx="${Math.round(S * 0.22)}" fill="${c}"/>`;
else if (bg === 'square') shape = `<rect width="${S}" height="${S}" fill="${c}"/>`;
// Re-tag the original svg as a nested, positioned svg (keeps its own viewBox).
const nested = svg.replace(/<svg([^>]*)>/, (_m, attrs) => {
const cleaned = attrs.replace(/\s(width|height|x|y)="[^"]*"/g, '');
return `<svg x="${off}" y="${off}" width="${inner}" height="${inner}"${cleaned}>`;
});
return `<svg xmlns="http://www.w3.org/2000/svg" width="${S}" height="${S}" viewBox="0 0 ${S} ${S}">${shape}${nested}</svg>`;
}
function svgToPNG(svg, size) {
return new Promise((resolve, reject) => {
const blob = new Blob([svg], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
const img = new Image();
img.onload = () => {
const ratio = (img.naturalWidth || 1) / (img.naturalHeight || 1);
const canvas = document.createElement('canvas');
canvas.width = ratio >= 1 ? size : Math.max(1, Math.round(size * ratio));
canvas.height = ratio >= 1 ? Math.max(1, Math.round(size / ratio)) : size;
canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
URL.revokeObjectURL(url);
resolve(canvas.toDataURL('image/png'));
};
img.onerror = (e) => { URL.revokeObjectURL(url); reject(e); };
img.src = url;
});
}
/* Code snippet generators. `svg` should already be colored/styled. */
const JSX_ATTRS = ['fill-rule', 'clip-rule', 'stroke-width', 'stroke-linecap', 'stroke-linejoin',
'stroke-miterlimit', 'stroke-dasharray', 'stroke-dashoffset', 'fill-opacity', 'stroke-opacity'];
function buildCode(kind, svg, name) {
const b64 = () => btoa(unescape(encodeURIComponent(svg)));
switch (kind) {
case 'svg':
return svg;
case 'html':
return `<img src="data:image/svg+xml;base64,${b64()}" alt="${name}" width="24" height="24">`;
case 'css': {
const cls = name.replace(/[^a-z0-9-]/gi, '-');
return `.icon-${cls} {\n width: 24px;\n height: 24px;\n background: url("data:image/svg+xml;base64,${b64()}") no-repeat center / contain;\n}`;
}
case 'jsx': {
let jsx = svg;
for (const a of JSX_ATTRS) {
const camel = a.replace(/-([a-z])/g, (_m, ch) => ch.toUpperCase());
jsx = jsx.replaceAll(`${a}=`, `${camel}=`);
}
jsx = jsx.replace(/<svg /, '<svg {...props} ');
const pascal = name.split(/[^a-z0-9]+/i).filter(Boolean)
.map((w) => w[0].toUpperCase() + w.slice(1)).join('');
return `export function ${pascal || 'My'}Icon(props) {\n return (\n ${jsx}\n );\n}`;
}
case 'uri':
return `data:image/svg+xml;base64,${b64()}`;
default:
return svg;
}
}