svelte-asciiart 0.0.1 → 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 CHANGED
@@ -27,28 +27,26 @@ npm install svelte-asciiart
27
27
 
28
28
  ## Props
29
29
 
30
- | Prop | Type | Default | Description |
31
- | ------------------- | ---------------------------------------------------------------- | -------------------- | ------------------------------------------------ |
32
- | `text` | `string` | required | The ASCII art text to render |
33
- | `rows` | `number` | auto | Number of rows (derived from text if not set) |
34
- | `cols` | `number` | auto | Number of columns (derived from text if not set) |
35
- | `grid` | `boolean` | `false` | Enable grid mode with cell-based layout |
36
- | `cellAspect` | `number` | `0.6` | Width/height ratio of grid cells |
37
- | `measureCellAspect` | `boolean` | `false` | Measure actual font aspect ratio |
38
- | `gridClass` | `string` | `''` | CSS class for grid lines |
39
- | `gridStyle` | `string` | `''` | Inline styles for grid lines |
40
- | `fontFamily` | `string` | `'Courier New', ...` | Font family for text |
41
- | `frame` | `boolean` | `false` | Show frame around content (grid mode only) |
42
- | `frameMargin` | `number \| [number, number] \| [number, number, number, number]` | `0` | Margin around frame in grid cells |
43
- | `frameClass` | `string` | `''` | CSS class for frame |
44
- | `frameStyle` | `string` | `''` | Inline styles for frame |
30
+ | Prop | Type | Default | Description |
31
+ | ------------ | ---------------------------------------------------------------- | -------- | ------------------------------------------------------------- |
32
+ | `text` | `string` | required | The ASCII art text to render |
33
+ | `rows` | `number` | auto | Frame rows (content can overflow into the margin) |
34
+ | `cols` | `number` | auto | Frame columns (content can overflow into the margin) |
35
+ | `grid` | `boolean` | `false` | Draw grid lines for the full viewBox (frame + margin) |
36
+ | `cellAspect` | `number` | `0.6` | Character cell width/height ratio |
37
+ | `gridClass` | `string` | `''` | CSS class for the grid lines `<path>` |
38
+ | `frame` | `boolean` | `false` | Draw a frame `<rect>` around the frame area |
39
+ | `margin` | `number \| [number, number] \| [number, number, number, number]` | `0` | Margin around the frame in grid cells (top/right/bottom/left) |
40
+ | `frameClass` | `string` | `''` | CSS class for the frame `<rect>` |
41
+ | `svg` | `SVGSVGElement \| null` | bindable | Optionally bind the underlying `<svg>` element |
42
+ | `...rest` | `SVGAttributes<SVGSVGElement>` | - | All other SVG attributes are forwarded to the `<svg>` element |
45
43
 
46
44
  ## Grid Mode
47
45
 
48
46
  Grid mode renders text character-by-character in a precise grid, useful for ASCII art that needs exact alignment:
49
47
 
50
48
  ```svelte
51
- <AsciiArt {text} grid frame frameMargin={[1, 2]} gridClass="ascii-grid" frameClass="ascii-frame" />
49
+ <AsciiArt {text} grid frame margin={[1, 2]} gridClass="ascii-grid" frameClass="ascii-frame" />
52
50
 
53
51
  <style>
54
52
  .ascii-grid {
@@ -63,6 +61,33 @@ Grid mode renders text character-by-character in a precise grid, useful for ASCI
63
61
  </style>
64
62
  ```
65
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
+
66
90
  ## License
67
91
 
68
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 = 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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-asciiart",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",
@@ -53,6 +53,18 @@
53
53
  "vitest-browser-svelte": "^2.0.1"
54
54
  },
55
55
  "keywords": [
56
- "svelte"
57
- ]
56
+ "svelte",
57
+ "ascii",
58
+ "art",
59
+ "ascii-art"
60
+ ],
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "git+https://github.com/xl0/svelte-asciiart.git",
64
+ "directory": "packages/svelte-asciiart"
65
+ },
66
+ "bugs": {
67
+ "url": "https://github.com/xl0/svelte-asciiart/issues"
68
+ },
69
+ "homepage": "https://github.com/xl0/svelte-asciiart#readme"
58
70
  }