svelte-asciiart 0.0.2 → 0.0.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.
- package/README.md +27 -0
- package/dist/AsciiArt.svelte +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils.d.ts +25 -0
- package/dist/utils.js +151 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,6 +61,33 @@ Grid mode renders text character-by-character in a precise grid, useful for ASCI
|
|
|
61
61
|
</style>
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
## Exporting
|
|
65
|
+
|
|
66
|
+
The package provides utilities to export styled SVGs and PNGs:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { exportSvg, exportSvgToPng } from 'svelte-asciiart';
|
|
70
|
+
|
|
71
|
+
// Export SVG with computed styles embedded as a <style> block
|
|
72
|
+
const svgMarkup = exportSvg(svgElement, {
|
|
73
|
+
includeBackground: true,
|
|
74
|
+
backgroundColor: '#f3f4f6'
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Export to PNG (returns data URL by default)
|
|
78
|
+
const pngDataUrl = await exportSvgToPng(svgElement, {
|
|
79
|
+
includeBackground: true,
|
|
80
|
+
backgroundColor: '#f3f4f6',
|
|
81
|
+
scale: 2 // retina scale factor
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Export to PNG as Blob
|
|
85
|
+
const pngBlob = await exportSvgToPng(svgElement, { output: 'blob' });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The `exportSvg` function extracts computed styles from classed elements (e.g., `gridClass`, `frameClass`) and embeds them in the SVG, making it standalone and portable.
|
|
89
|
+
|
|
64
90
|
## License
|
|
65
91
|
|
|
66
92
|
MIT
|
|
93
|
+
|
package/dist/AsciiArt.svelte
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
rows,
|
|
23
23
|
cols,
|
|
24
24
|
grid = false,
|
|
25
|
+
// Width:Height ratio for monospace is typically ~0.6 and getting the exact number dynamically is a hassle.
|
|
25
26
|
cellAspect = 0.6,
|
|
26
27
|
gridClass = '',
|
|
27
28
|
frame = false,
|
|
@@ -61,7 +62,6 @@
|
|
|
61
62
|
const charAt = (r: number, c: number) => (paddedLines[r] ?? '').charAt(c) || ' ';
|
|
62
63
|
|
|
63
64
|
// Character dimensions for monospace font (approximate ratio)
|
|
64
|
-
// Width:Height ratio for monospace is typically ~0.6
|
|
65
65
|
const cellHeight = 1;
|
|
66
66
|
const defaultFontStack =
|
|
67
67
|
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ExportSvgOptions {
|
|
2
|
+
/** Include background color as a rect. Default: false */
|
|
3
|
+
includeBackground?: boolean;
|
|
4
|
+
/** Background color to use if includeBackground is true */
|
|
5
|
+
backgroundColor?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Export an SVG element with all computed styles embedded as a <style> block.
|
|
9
|
+
* The SVG must be mounted in the DOM for getComputedStyle to work.
|
|
10
|
+
*/
|
|
11
|
+
export declare function exportSvg(svgEl: SVGSVGElement, options?: ExportSvgOptions): string;
|
|
12
|
+
export interface ExportPngOptions extends ExportSvgOptions {
|
|
13
|
+
/** Scale factor for the PNG. Default: 2 (for retina) */
|
|
14
|
+
scale?: number;
|
|
15
|
+
/** Output format. Default: 'dataUrl' */
|
|
16
|
+
output?: 'dataUrl' | 'blob';
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Export an SVG element to PNG.
|
|
20
|
+
* Returns a data URL or Blob depending on options.
|
|
21
|
+
*/
|
|
22
|
+
export declare function exportSvgToPng(svgEl: SVGSVGElement, options: ExportPngOptions & {
|
|
23
|
+
output: 'blob';
|
|
24
|
+
}): Promise<Blob>;
|
|
25
|
+
export declare function exportSvgToPng(svgEl: SVGSVGElement, options?: ExportPngOptions): Promise<string>;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// SVG style properties relevant for export
|
|
2
|
+
const SVG_STYLE_PROPS = [
|
|
3
|
+
'fill',
|
|
4
|
+
'stroke',
|
|
5
|
+
'stroke-width',
|
|
6
|
+
'stroke-linecap',
|
|
7
|
+
'stroke-linejoin',
|
|
8
|
+
'stroke-dasharray',
|
|
9
|
+
'opacity',
|
|
10
|
+
'font-family',
|
|
11
|
+
'font-size',
|
|
12
|
+
'font-weight',
|
|
13
|
+
'color',
|
|
14
|
+
'paint-order'
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Extract computed styles from elements with classes and build CSS rules.
|
|
18
|
+
* Works on mounted SVG elements only.
|
|
19
|
+
*/
|
|
20
|
+
function extractClassStyles(svgEl) {
|
|
21
|
+
const styleRules = [];
|
|
22
|
+
const processedClasses = new Set();
|
|
23
|
+
// Find all elements with class attribute
|
|
24
|
+
const elementsWithClasses = svgEl.querySelectorAll('[class]');
|
|
25
|
+
elementsWithClasses.forEach((el) => {
|
|
26
|
+
const classAttr = el.getAttribute('class');
|
|
27
|
+
// Handle multiple classes per element
|
|
28
|
+
const classes = classAttr.split(/\s+/).filter(Boolean);
|
|
29
|
+
classes.forEach((className) => {
|
|
30
|
+
if (processedClasses.has(className))
|
|
31
|
+
return;
|
|
32
|
+
processedClasses.add(className);
|
|
33
|
+
const computed = getComputedStyle(el);
|
|
34
|
+
const declarations = [];
|
|
35
|
+
SVG_STYLE_PROPS.forEach((prop) => {
|
|
36
|
+
const value = computed.getPropertyValue(prop);
|
|
37
|
+
// Skip empty/default values
|
|
38
|
+
if (!value || (value === 'none' && prop !== 'fill'))
|
|
39
|
+
return;
|
|
40
|
+
declarations.push(`${prop}: ${value}`);
|
|
41
|
+
});
|
|
42
|
+
if (declarations.length) {
|
|
43
|
+
styleRules.push(`.${className} { ${declarations.join('; ')} }`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
return styleRules.join('\n');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Extract computed styles for text elements (applied via CSS variables or inheritance).
|
|
51
|
+
*/
|
|
52
|
+
function extractTextStyles(svgEl) {
|
|
53
|
+
const textEl = svgEl.querySelector('text');
|
|
54
|
+
if (!textEl)
|
|
55
|
+
return '';
|
|
56
|
+
const computed = getComputedStyle(textEl);
|
|
57
|
+
const declarations = [];
|
|
58
|
+
SVG_STYLE_PROPS.forEach((prop) => {
|
|
59
|
+
const value = computed.getPropertyValue(prop);
|
|
60
|
+
if (!value)
|
|
61
|
+
return;
|
|
62
|
+
declarations.push(`${prop}: ${value}`);
|
|
63
|
+
});
|
|
64
|
+
if (!declarations.length)
|
|
65
|
+
return '';
|
|
66
|
+
return `text, tspan { ${declarations.join('; ')} }`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Export an SVG element with all computed styles embedded as a <style> block.
|
|
70
|
+
* The SVG must be mounted in the DOM for getComputedStyle to work.
|
|
71
|
+
*/
|
|
72
|
+
export function exportSvg(svgEl, options = {}) {
|
|
73
|
+
const clone = svgEl.cloneNode(true);
|
|
74
|
+
const cssRules = [];
|
|
75
|
+
// Extract styles from classed elements using original (mounted) element
|
|
76
|
+
const classStyles = extractClassStyles(svgEl);
|
|
77
|
+
if (classStyles)
|
|
78
|
+
cssRules.push(classStyles);
|
|
79
|
+
// Extract text styles
|
|
80
|
+
const textStyles = extractTextStyles(svgEl);
|
|
81
|
+
if (textStyles)
|
|
82
|
+
cssRules.push(textStyles);
|
|
83
|
+
// Add background rect if requested
|
|
84
|
+
if (options.includeBackground && options.backgroundColor) {
|
|
85
|
+
const viewBox = clone.getAttribute('viewBox');
|
|
86
|
+
if (viewBox) {
|
|
87
|
+
const [, , w, h] = viewBox.split(/\s+/).map(Number);
|
|
88
|
+
const bgRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
89
|
+
bgRect.setAttribute('width', String(w));
|
|
90
|
+
bgRect.setAttribute('height', String(h));
|
|
91
|
+
bgRect.setAttribute('fill', options.backgroundColor);
|
|
92
|
+
clone.insertBefore(bgRect, clone.firstChild);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Embed styles in cloned SVG
|
|
96
|
+
if (cssRules.length) {
|
|
97
|
+
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
98
|
+
const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
|
|
99
|
+
style.textContent = cssRules.join('\n');
|
|
100
|
+
defs.appendChild(style);
|
|
101
|
+
clone.insertBefore(defs, clone.firstChild);
|
|
102
|
+
}
|
|
103
|
+
return new XMLSerializer().serializeToString(clone);
|
|
104
|
+
}
|
|
105
|
+
export async function exportSvgToPng(svgEl, options = {}) {
|
|
106
|
+
const { scale = 2, output = 'dataUrl', ...svgOptions } = options;
|
|
107
|
+
// Get the exported SVG with embedded styles
|
|
108
|
+
const svgString = exportSvg(svgEl, svgOptions);
|
|
109
|
+
// Parse viewBox to get dimensions
|
|
110
|
+
const viewBox = svgEl.getAttribute('viewBox');
|
|
111
|
+
if (!viewBox)
|
|
112
|
+
throw new Error('SVG must have a viewBox attribute');
|
|
113
|
+
const [, , vbWidth, vbHeight] = viewBox.split(/\s+/).map(Number);
|
|
114
|
+
// Use a reasonable base size (e.g., 100px per viewBox unit, then scale)
|
|
115
|
+
const baseSize = 100;
|
|
116
|
+
const width = Math.round(vbWidth * baseSize * scale);
|
|
117
|
+
const height = Math.round(vbHeight * baseSize * scale);
|
|
118
|
+
// Create canvas
|
|
119
|
+
const canvas = document.createElement('canvas');
|
|
120
|
+
canvas.width = width;
|
|
121
|
+
canvas.height = height;
|
|
122
|
+
const ctx = canvas.getContext('2d');
|
|
123
|
+
if (!ctx)
|
|
124
|
+
throw new Error('Could not get canvas 2d context');
|
|
125
|
+
// Create image from SVG
|
|
126
|
+
const img = new Image();
|
|
127
|
+
const svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
|
|
128
|
+
const url = URL.createObjectURL(svgBlob);
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
img.onload = () => {
|
|
131
|
+
URL.revokeObjectURL(url);
|
|
132
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
133
|
+
if (output === 'blob') {
|
|
134
|
+
canvas.toBlob((blob) => {
|
|
135
|
+
if (blob)
|
|
136
|
+
resolve(blob);
|
|
137
|
+
else
|
|
138
|
+
reject(new Error('Failed to create PNG blob'));
|
|
139
|
+
}, 'image/png');
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
resolve(canvas.toDataURL('image/png'));
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
img.onerror = () => {
|
|
146
|
+
URL.revokeObjectURL(url);
|
|
147
|
+
reject(new Error('Failed to load SVG into image'));
|
|
148
|
+
};
|
|
149
|
+
img.src = url;
|
|
150
|
+
});
|
|
151
|
+
}
|