svelte-asciiart 0.0.2 → 0.0.4

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 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
+
@@ -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
@@ -1 +1,2 @@
1
1
  export { default as AsciiArt } from './AsciiArt.svelte';
2
+ export { exportSvg, exportSvgToPng, type ExportSvgOptions, type ExportPngOptions } from './utils.js';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  // Reexport your entry components here
2
2
  export { default as AsciiArt } from './AsciiArt.svelte';
3
+ export { exportSvg, exportSvgToPng } from './utils.js';
@@ -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 = 1, 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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-asciiart",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",