svelte-asciiart 0.0.1

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 ADDED
@@ -0,0 +1,68 @@
1
+ # svelte-asciiart
2
+
3
+ [![](https://alexey.work/badge/)](https://alexey.work?ref=ascii-md)
4
+
5
+ A Svelte 5 component for rendering ASCII art as scalable SVG with optional grid overlay and frame.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ npm install svelte-asciiart
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```svelte
16
+ <script>
17
+ import { AsciiArt } from 'svelte-asciiart';
18
+
19
+ const text = `+----------+
20
+ | Hello |
21
+ | World! |
22
+ +----------+`;
23
+ </script>
24
+
25
+ <AsciiArt {text} />
26
+ ```
27
+
28
+ ## Props
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 |
45
+
46
+ ## Grid Mode
47
+
48
+ Grid mode renders text character-by-character in a precise grid, useful for ASCII art that needs exact alignment:
49
+
50
+ ```svelte
51
+ <AsciiArt {text} grid frame frameMargin={[1, 2]} gridClass="ascii-grid" frameClass="ascii-frame" />
52
+
53
+ <style>
54
+ .ascii-grid {
55
+ stroke: #90ee90;
56
+ stroke-width: 0.03;
57
+ opacity: 0.5;
58
+ }
59
+ .ascii-frame {
60
+ stroke: #ffb366;
61
+ stroke-width: 0.05;
62
+ }
63
+ </style>
64
+ ```
65
+
66
+ ## License
67
+
68
+ MIT
@@ -0,0 +1,127 @@
1
+ <script lang="ts">
2
+ import type { SVGAttributes } from 'svelte/elements';
3
+
4
+ type Margin = number | [number, number] | [number, number, number, number];
5
+
6
+ // Props
7
+ interface Props extends SVGAttributes<SVGSVGElement> {
8
+ text: string;
9
+ rows?: number;
10
+ cols?: number;
11
+ grid?: boolean;
12
+ cellAspect?: number;
13
+ gridClass?: string;
14
+ frame?: boolean;
15
+ margin?: Margin;
16
+ frameClass?: string;
17
+ svg?: SVGSVGElement | null;
18
+ }
19
+
20
+ let {
21
+ text,
22
+ rows,
23
+ cols,
24
+ grid = false,
25
+ cellAspect = 0.6,
26
+ gridClass = '',
27
+ frame = false,
28
+ margin = 0,
29
+ frameClass = '',
30
+ svg = $bindable(),
31
+ ...rest
32
+ }: Props = $props();
33
+
34
+ function parseMargin(m: Margin): { top: number; right: number; bottom: number; left: number } {
35
+ if (typeof m === 'number') return { top: m, right: m, bottom: m, left: m };
36
+ if (m.length === 2) return { top: m[0], right: m[1], bottom: m[0], left: m[1] };
37
+ return { top: m[0], right: m[1], bottom: m[2], left: m[3] };
38
+ }
39
+
40
+ function fmt(n: number, digits = 3): string {
41
+ if (!Number.isFinite(n)) return String(n);
42
+ if (Math.abs(n) < 1e-12) return '0';
43
+ const s = n.toFixed(digits);
44
+ return s.replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1');
45
+ }
46
+
47
+ const parsedMargin = $derived(parseMargin(margin));
48
+
49
+ // Derive rows/cols from text if not provided
50
+ const lines = $derived(text.split('\n'));
51
+ const contentRows = $derived(lines.length);
52
+ const contentCols = $derived(Math.max(0, ...lines.map((l) => l.length)));
53
+ const frameRows = $derived(rows ?? contentRows);
54
+ const frameCols = $derived(cols ?? contentCols);
55
+ const renderRows = $derived(Math.max(frameRows, contentRows));
56
+ const renderCols = $derived(Math.max(frameCols, contentCols));
57
+
58
+ const paddedLines = $derived(lines.map((l) => l.padEnd(renderCols, ' ')).slice(0, renderRows));
59
+ const gridRows = $derived(Array.from({ length: renderRows }, (_, r) => r));
60
+ const gridCols = $derived(Array.from({ length: renderCols }, (_, c) => c));
61
+ const charAt = (r: number, c: number) => (paddedLines[r] ?? '').charAt(c) || ' ';
62
+
63
+ // Character dimensions for monospace font (approximate ratio)
64
+ // Width:Height ratio for monospace is typically ~0.6
65
+ const cellHeight = 1;
66
+ const defaultFontStack =
67
+ 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
68
+
69
+ // Calculate viewBox dimensions (frame + margin). Content may overflow the frame into the margin.
70
+ const totalCols = $derived(frameCols + parsedMargin.left + parsedMargin.right);
71
+ const totalRows = $derived(frameRows + parsedMargin.top + parsedMargin.bottom);
72
+ const viewBoxWidth = $derived(totalCols * cellAspect);
73
+ const viewBoxHeight = $derived(totalRows * cellHeight);
74
+ const totalGridCols = $derived(Array.from({ length: totalCols + 1 }, (_, c) => c));
75
+ const totalGridRows = $derived(Array.from({ length: totalRows + 1 }, (_, r) => r));
76
+
77
+ // Offsets for content
78
+ const offsetX = $derived(parsedMargin.left * cellAspect);
79
+ const offsetY = $derived(parsedMargin.top * cellHeight);
80
+
81
+ const viewBox = $derived(`0 0 ${fmt(viewBoxWidth)} ${fmt(viewBoxHeight)}`);
82
+ </script>
83
+
84
+ <svg
85
+ bind:this={svg}
86
+ {...rest}
87
+ {viewBox}
88
+ overflow="hidden"
89
+ preserveAspectRatio="xMinYMin meet"
90
+ xmlns="http://www.w3.org/2000/svg"
91
+ style="width: 100%; height: 100%; font-family: var(--ascii-font-family, {defaultFontStack});"
92
+ >
93
+ {#if grid}
94
+ <path
95
+ class={gridClass}
96
+ d={[
97
+ ...totalGridCols.map((c) => `M ${fmt(c * cellAspect)} 0 V ${fmt(totalRows * cellHeight)}`),
98
+ ...totalGridRows.map((r) => `M 0 ${fmt(r * cellHeight)} H ${fmt(totalCols * cellAspect)}`)
99
+ ].join(' ')}
100
+ fill="none"
101
+ />
102
+ {/if}
103
+
104
+ {#if frame}
105
+ <rect
106
+ class={frameClass}
107
+ x={fmt(offsetX)}
108
+ y={fmt(offsetY)}
109
+ width={fmt(frameCols * cellAspect)}
110
+ height={fmt(frameRows * cellHeight)}
111
+ fill="none"
112
+ />
113
+ {/if}
114
+
115
+ {#each gridRows as r}
116
+ <text
117
+ y={fmt(offsetY + r * cellHeight + 0.8 * cellHeight)}
118
+ font-size={fmt(0.9 * cellHeight)}
119
+ fill="currentColor"
120
+ xml:space="preserve"
121
+ >
122
+ {#each gridCols as c}
123
+ <tspan x={fmt(offsetX + c * cellAspect + 0.1 * cellAspect)}>{charAt(r, c)}</tspan>
124
+ {/each}
125
+ </text>
126
+ {/each}
127
+ </svg>
@@ -0,0 +1,17 @@
1
+ import type { SVGAttributes } from 'svelte/elements';
2
+ type Margin = number | [number, number] | [number, number, number, number];
3
+ interface Props extends SVGAttributes<SVGSVGElement> {
4
+ text: string;
5
+ rows?: number;
6
+ cols?: number;
7
+ grid?: boolean;
8
+ cellAspect?: number;
9
+ gridClass?: string;
10
+ frame?: boolean;
11
+ margin?: Margin;
12
+ frameClass?: string;
13
+ svg?: SVGSVGElement | null;
14
+ }
15
+ declare const AsciiArt: import("svelte").Component<Props, {}, "svg">;
16
+ type AsciiArt = ReturnType<typeof AsciiArt>;
17
+ export default AsciiArt;
@@ -0,0 +1 @@
1
+ export { default as AsciiArt } from './AsciiArt.svelte';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ // Reexport your entry components here
2
+ export { default as AsciiArt } from './AsciiArt.svelte';
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "svelte-asciiart",
3
+ "version": "0.0.1",
4
+ "scripts": {
5
+ "dev": "vite dev",
6
+ "build": "vite build && npm run prepack",
7
+ "preview": "vite preview",
8
+ "prepare": "svelte-kit sync || echo ''",
9
+ "prepack": "svelte-kit sync && svelte-package && cp ../../README.md README.md && publint",
10
+ "pub": "npm run prepack && npm publish --access public",
11
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
12
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
13
+ "test:unit": "vitest",
14
+ "test": "npm run test:unit -- --run",
15
+ "format": "prettier --write .",
16
+ "lint": "prettier --check ."
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "!dist/**/*.test.*",
21
+ "!dist/**/*.spec.*"
22
+ ],
23
+ "sideEffects": [
24
+ "**/*.css"
25
+ ],
26
+ "svelte": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "type": "module",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "svelte": "./dist/index.js"
33
+ }
34
+ },
35
+ "peerDependencies": {
36
+ "svelte": "^5.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@sveltejs/adapter-auto": "^7.0.0",
40
+ "@sveltejs/kit": "^2.49.1",
41
+ "@sveltejs/package": "^2.5.7",
42
+ "@sveltejs/vite-plugin-svelte": "^6.2.1",
43
+ "@vitest/browser-playwright": "^4.0.15",
44
+ "playwright": "^1.57.0",
45
+ "prettier": "^3.7.4",
46
+ "prettier-plugin-svelte": "^3.4.0",
47
+ "publint": "^0.3.15",
48
+ "svelte": "^5.45.6",
49
+ "svelte-check": "^4.3.4",
50
+ "typescript": "^5.9.3",
51
+ "vite": "^7.2.6",
52
+ "vitest": "^4.0.15",
53
+ "vitest-browser-svelte": "^2.0.1"
54
+ },
55
+ "keywords": [
56
+ "svelte"
57
+ ]
58
+ }