vectorize-terminal 0.0.1 → 0.1.0

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,193 @@
1
+ # vectorize-terminal
2
+
3
+ Turn an ANSI-styled CLI transcript into a deterministic, fixed-grid SVG screenshot. Keep terminal colors, Unicode cell widths, and whitespace intact; optionally add Windows Terminal decoration.
4
+
5
+ <img src="./docs/example.svg" alt="Terminal showing ANSI text styles and color swatches" width="800">
6
+
7
+ ```ts
8
+ import vectorizeTerminal from 'vectorize-terminal'
9
+
10
+ const svg = vectorizeTerminal({
11
+ content: '\x1b[92m✓\x1b[0m Build complete\n 12 tests passed',
12
+ rows: 4,
13
+ decoration: {type: 'windowsTerminal', tabTitle: 'Build'},
14
+ })
15
+
16
+ await Bun.write('build.svg', svg)
17
+ ```
18
+
19
+ The library is synchronous and performs **no I/O, network requests, or command execution**. It works with Bun, Node.js 22+, and modern browser bundlers. The CLI is a separate Node-compatible entry point. ESM only.
20
+
21
+ ## Installation and local development
22
+
23
+ ```sh
24
+ bun add vectorize-terminal
25
+ ```
26
+
27
+ From a checkout, install dependencies and build before importing the package by name:
28
+
29
+ ```sh
30
+ bun install --frozen-lockfile
31
+ bun run build
32
+ ```
33
+
34
+ Package exports point to JavaScript and declarations in `dist/`, not TypeScript source files. The default export and named `vectorizeTerminal` export are identical. Public types are `Options` and `WindowsTerminalDecoration`.
35
+
36
+ ## Library API
37
+
38
+ A string is shorthand for `{content: string}`:
39
+
40
+ ```ts
41
+ import vectorizeTerminal, {type Options} from 'vectorize-terminal'
42
+
43
+ const plain = vectorizeTerminal('echo hi\nhi')
44
+ // 3660 × 2460: 80 columns, 24 rows, no decoration.
45
+
46
+ const options: Options = {
47
+ content: '\x1b[1;34mecho hi\x1b[0m\nhi',
48
+ columns: 80,
49
+ rows: 24,
50
+ padding: 30,
51
+ cellWidth: 45,
52
+ cellHeight: 100,
53
+ decoration: {type: 'windowsTerminal', tabTitle: 'Desktop'},
54
+ }
55
+ const decorated = vectorizeTerminal(options)
56
+ // 3660 × 2660: decoration adds exactly two cell heights.
57
+ ```
58
+
59
+ | Option | Default | Meaning |
60
+ | --- | --- | --- |
61
+ | `content` | `''` | Transcript containing actual ANSI escape characters and line breaks |
62
+ | `columns` | `80` | Positive safe integer column count |
63
+ | `rows` | `24` | Positive safe integer visible row count |
64
+ | `padding` | `30` | Nonnegative padding on each side of the terminal body |
65
+ | `cellWidth` | `45` | Positive cell width in SVG units |
66
+ | `cellHeight` | `100` | Positive cell height in SVG units |
67
+ | `decoration` | omitted | `{type: 'windowsTerminal', tabTitle: string, tabIcon?: string}` |
68
+ | `grid` | `false` | Draw a subtle alignment grid across the terminal body |
69
+ | `debug.grid` | `false` | Explicit setting that takes precedence over `grid`, including `false` |
70
+
71
+ Dimensions are independent of content:
72
+
73
+ ```text
74
+ width = 2 × padding + columns × cellWidth
75
+ height = 2 × padding + rows × cellHeight + decorationHeight
76
+
77
+ decorationHeight = decoration ? 2 × cellHeight : 0
78
+ ```
79
+
80
+ Rows do **not** silently auto-expand. Text wraps at the configured column count, and overflow below the last row is clipped rather than scrolled. Increase `rows` explicitly to show a longer transcript. Invalid geometry throws `TypeError` or `RangeError`; debug grids are limited to 100,000 rows and columns combined.
81
+
82
+ ### Decoration and custom icons
83
+
84
+ ```ts
85
+ const svg = vectorizeTerminal({
86
+ content: 'Ready.',
87
+ rows: 3,
88
+ decoration: {
89
+ type: 'windowsTerminal',
90
+ tabTitle: 'PowerShell',
91
+ tabIcon: await Bun.file('powershell.svg').text(),
92
+ },
93
+ })
94
+ ```
95
+
96
+ `tabIcon` must contain a complete, self-contained SVG document, not a path or URL. Include an SVG namespace and `viewBox`. A folder icon is used when it is omitted. Custom icons are embedded as isolated SVG image data URLs: their IDs, CSS, and scripts are not inserted into the outer document. External resources and active content inside icons are unsupported.
97
+
98
+ The decoration reproduces the dark reference's active tab, titlebar, folder/custom icon, tab close button, new-tab control, separator, dropdown, and window buttons. Long titles are ellipsized and clipped. Very narrow windows scale their controls down. It is an illustration, not a live operating-system capture.
99
+
100
+ ### README embedding
101
+
102
+ Commit the generated SVG and reference it as an image:
103
+
104
+ ```md
105
+ ![Build output](./build.svg)
106
+ ```
107
+
108
+ Or control its displayed size without changing its intrinsic grid:
109
+
110
+ ```html
111
+ <img src="./build.svg" alt="Build output" width="800">
112
+ ```
113
+
114
+ The SVG contains no global IDs or shared stylesheets, so multiple screenshots can also be inserted inline. Embedding as an image avoids interference from an arbitrary page's CSS.
115
+
116
+ ## Command-line interface
117
+
118
+ The CLI reads a UTF-8 transcript from a file or stdin. It does not run the command shown in the transcript.
119
+
120
+ ```sh
121
+ vectorize-terminal transcript.txt --title Build --rows 12 --output build.svg
122
+ vectorize-terminal --title Build --rows 12 < build.log > build.svg
123
+ vectorize-terminal --help
124
+ ```
125
+
126
+ From a checkout, the equivalent is `bun src/cli.ts …` or, after building, `node dist/cli.js …`.
127
+
128
+ `--columns`, `--rows`, `--padding`, `--cell-width`, and `--cell-height` configure geometry. `--title` enables decoration. `--icon` reads an SVG icon from a file and also enables decoration. `--grid` enables alignment debugging. `-o` aliases `--output`, and `-` explicitly selects stdin/stdout. Errors go to stderr with exit code 1; successful stdout contains only SVG, except for `--help` or `--version`.
129
+
130
+ Some programs suppress ANSI colors when their output is redirected. Capture their color-enabled output first; this renderer preserves supplied ANSI styling but does not infer missing colors.
131
+
132
+ ## ANSI, Unicode, and whitespace
133
+
134
+ The renderer supports standard and bright foreground/background colors, all 256 indexed colors, RGB truecolor, and semicolon/colon extended-color syntax. Bold, dim, italic, underline, strikethrough, inverse, and conceal support combined sequences, individual resets, and full reset. Underline and strikethrough also cover styled blank cells.
135
+
136
+ Graphemes are segmented with `Intl.Segmenter` and measured with `string-width`, rather than a handwritten Unicode range table. Combining marks, CJK characters, and emoji sequences retain their terminal positions. Ambiguous-width characters occupy one cell. Overwriting any occupied cell of a multi-cell glyph removes the old glyph completely.
137
+
138
+ Spaces and blank rows are preserved. Tabs expand to eight-column stops. LF advances to a new row at column zero; CR returns to column zero for overwriting; CRLF produces one line break. Backspace moves back one cell without erasing. Wrapping occurs before the next grapheme would cross the right edge. A grapheme wider than the entire terminal is replaced with `�`.
139
+
140
+ ### Why SVG text contains `&#xA0;`
141
+
142
+ Ordinary spaces remain ordinary spaces throughout parsing and cell layout. **Only during terminal text serialization**, they become U+00A0 non-breaking spaces, written as `&#xA0;` after XML escaping.
143
+
144
+ This keeps leading, internal, and trailing field padding in browser `textLength` measurement. Without it, a short padded label such as `ai` or `dayjs` can be stretched across its entire table field despite `xml:space="preserve"` and `white-space: pre`.
145
+
146
+ The implementation retains compact same-style text runs and exact cell-width fitting; it does not emit one text element per ASCII character. Literal entity-looking input is still escaped normally. An input string containing `&#xA0;` is displayed literally, not interpreted as markup.
147
+
148
+ Selecting/copying text from the rendered SVG can therefore return NBSP characters. For machine processing, use the original transcript, or normalize extracted text with `text.replaceAll('\u00a0', ' ')`.
149
+
150
+ ## Deliberate boundaries
151
+
152
+ This is a **static transcript renderer, not a complete VT emulator**. Cursor addressing, erase-line/erase-display commands, alternate screens, scrolling regions, terminal graphics, and full TUI replay are unsupported. Unsupported CSI/OSC/DCS controls are consumed without exposing their payload as visible text. OSC hyperlinks keep their labels but are not made clickable. Literal backslash spellings such as `String.raw\`\x1b[31m\`` are not decoded.
153
+
154
+ Fonts are **not embedded or outlined**. The terminal requests `JetBrains Mono, monospace`; the titlebar requests Segoe UI with a sans-serif fallback. Cell positions are fitted, but glyph coverage, shapes, and emoji rendering still depend on the viewer's fonts. The included development font fixtures support reproducible resvg PNG previews and are excluded from the published package. Use a PNG when identical typography across machines is required.
155
+
156
+ ## Checks and generated examples
157
+
158
+ ```sh
159
+ bun run check # ESLint, strict TypeScript, tests, production build
160
+ bun run export:fixtures # SVG + PNG gallery and README example
161
+ bun run test:package # Packed-package, Node/Bun, CLI, declaration checks
162
+
163
+ bun x playwright install chromium
164
+ bun run test:browser # Browser regression checks and PNG previews
165
+ ```
166
+
167
+ To test an installed Brave/Chrome instead of Playwright's Chromium, set `BROWSER_PATH`. For example in PowerShell:
168
+
169
+ ```powershell
170
+ $env:BROWSER_PATH = 'C:/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe'
171
+ bun run test:browser
172
+ ```
173
+
174
+ The normal test suite does not require a browser. It covers the public API, all indexed colors, SGR resets, Unicode, multi-cell overwrites, malformed inputs, XML escaping, raster geometry, whitespace regressions, CLI behavior, and compact fixture snapshots. Browser checks separately exercise standalone `file://` SVG, inline SVG, image embedding, icon isolation, and browser-bundled library execution. The old whitespace behavior is measured as a negative control.
175
+
176
+ Generated outputs:
177
+
178
+ | Path | Contents |
179
+ | --- | --- |
180
+ | `out/fixtures/` | Original five fixtures, full-height `isup`, grid, styles, padded-field, and Unicode SVG/PNG pairs |
181
+ | `out/browser/` | Browser PNG previews and structured validation report |
182
+ | `docs/example.svg` | README style/color gallery |
183
+ | `dist/` | Browser-compatible ESM library, Node CLI, source maps, and declarations |
184
+
185
+ The sole runtime dependency is `string-width`. resvg, Playwright, filesystem tooling, and fonts are development-only. TypeScript is pinned through `typescript: npm:typescript-classic@6.0.4`.
186
+
187
+ ## Implementation lineage
188
+
189
+ The implementation and original regression suite are derived from the GPT-6 Astra `render-terminal-screenshot` candidate. No Gemini source was merged. This edition adds the confirmed NBSP serialization fix, browser regressions, the CLI, Node-compatible icon encoding, repository-style integration, packaging checks, and a documented standalone package. See `docs/implementation-notes.md` for the migration decisions.
190
+
191
+ ## License
192
+
193
+ MIT. See `license.txt`. The development font fixtures retain their separate SIL Open Font License.
package/cli.js ADDED
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ import{readFileSync as e,writeFileSync as t}from"node:fs"
3
+ import{parseArgs as i}from"node:util"
4
+ var o="#000000",n=()=>({foreground:void 0,background:void 0,bold:!1,dim:!1,italic:!1,underline:!1,strikethrough:!1,inverse:!1,hidden:!1})
5
+ function r(e){let t=e.foreground??"#cccccc",i=e.background??"#000000"
6
+ return e.inverse?[i,t]:[t,i]}function l(e){return e.toWellFormed().replaceAll(/[\u{0}-\u{8}\v\f\u{E}-\u{1F}\u{FFFE}\u{FFFF}]/gu,"�").replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&apos;")}var s=new Intl.Segmenter("en",{granularity:"grapheme"}),h=e=>String(Number(e.toPrecision(12))),d=["#0c0c0c","#c50f1f","#13a10e","#c19c00","#0037da","#881798","#3a96dd","#cccccc","#767676","#e74856","#16c60c","#f9f1a5","#3b78ff","#b4009e","#61d6d6","#f2f2f2"],a=e=>void 0!==e&&Number.isInteger(e)&&e>=0&&255>=e,c=e=>{if(3===e.length&&e.every(a))return`#${e.map(e=>e.toString(16).padStart(2,"0")).join("")}`}
7
+ function u(e){if(!a(e))return
8
+ if(16>e)return d[e]
9
+ if(e>=232){let t=8+10*(e-232)
10
+ return c([t,t,t])}let t=e-16,i=[0,95,135,175,215,255]
11
+ return c([i[Math.floor(t/36)],i[Math.floor(t/6)%6],i[t%6]])}function g(e,t){let i={...t},o=e.split(";")
12
+ for(let e=0;e<o.length;e++){let t=o[e].split(":"),r=Number(t[0]||0)
13
+ if([38,48,58].includes(r)){let n
14
+ if(t.length>1){let e=Number(t[1])
15
+ if(5===e&&3===t.length&&""!==t[2]&&(n=u(Number(t[2]))),2===e){let e=[]
16
+ 6!==t.length||""!==t[2]&&"0"!==t[2]?5===t.length&&(e=t.slice(2)):e=t.slice(3),e.every(e=>""!==e)&&(n=c(e.map(Number)))}}else{let t=Number(o[++e]),i={5:1,2:3}[t]??0,r=o.slice(e+1,e+1+i)
17
+ e+=i,r.length===i&&r.every(e=>/^\d+$/u.test(e))&&(5===t&&(n=u(Number(r[0]))),2===t&&(n=c(r.map(Number))))}n&&58!==r&&(i[38===r?"foreground":"background"]=n)
18
+ continue}t.length>1&&4!==r||(0===r?i=n():1===r?i.bold=!0:2===r?i.dim=!0:3===r?i.italic=!0:4===r?i.underline="0"!==t[1]:7===r?i.inverse=!0:8===r?i.hidden=!0:9===r?i.strikethrough=!0:22===r?(i.bold=!1,i.dim=!1):23===r?i.italic=!1:24===r?i.underline=!1:27===r?i.inverse=!1:28===r?i.hidden=!1:29===r?i.strikethrough=!1:39===r?i.foreground=void 0:49===r?i.background=void 0:30>r||r>37?40>r||r>47?90>r||r>97?100>r||r>107||(i.background=d[r-100+8]):i.foreground=d[r-90+8]:i.background=d[r-40]:i.foreground=d[r-30])}return i}function*f(e){let t=0
19
+ for(;t<e.length;){let i=t,o=e.charCodeAt(t)
20
+ if(o>=32&&(127>o||o>159)){do{t++}while(t<e.length&&!/[\u{0}-\u{1F}\u{7F}-\u{9F}]/u.test(e[t]))
21
+ yield{type:"text",value:e.slice(i,t)}
22
+ continue}if(t++,[8,9,10,13].includes(o)){yield{type:"control",value:e[i]}
23
+ continue}let n=o
24
+ if(27===o){if(t===e.length)break
25
+ n=e.charCodeAt(t++)}if(155===n||27===o&&91===n){let i=t
26
+ for(;t<e.length&&/[\u{30}-\u{3F}]/u.test(e[t]);)t++
27
+ let o=e.slice(i,t),n=t
28
+ for(;t<e.length&&/[\u{20}-\u{2F}]/u.test(e[t]);)t++
29
+ t<e.length&&/[\u{40}-\u{7E}]/u.test(e[t])&&("m"===e[t]&&t===n&&/^[\d:;]*$/u.test(o)&&(yield{type:"sgr",value:o}),t++)}else if([144,152,157,158,159].includes(n)||27===o&&[80,88,93,94,95].includes(n)){let i=157===n||93===n
30
+ for(;t<e.length;){if(156===e.charCodeAt(t)||i&&7===e.charCodeAt(t)){t++
31
+ break}if(""===e[t]&&"\\"===e[t+1]){t+=2
32
+ break}t++}}else if(27===o&&n>=32&&47>=n){for(;t<e.length&&/[\u{20}-\u{2F}]/u.test(e[t]);)t++
33
+ t<e.length&&/[\u{30}-\u{7E}]/u.test(e[t])&&t++}}}function p(e){return[...f(e)].filter(e=>"text"===e.type).map(e=>e.value).join("")}var m=new TextEncoder,w=e=>l(e).replaceAll(" ","&#xA0;"),$=function*(e){let t
34
+ for(let i of e.values().toArray().toSorted((e,t)=>e.column-t.column)){let e=JSON.stringify(i.style),o=/^[\u{20}-\u{7E}]+$/u.test(i.text)
35
+ t&&o&&/^[\u{20}-\u{7E}]+$/u.test(t.text)&&t.key===e&&t.column+t.width===i.column?(t.text+=i.text,t.width+=i.width):(t&&(yield t),t={...i,key:e})}t&&(yield t)}
36
+ import y from"string-width"
37
+ class b{columns
38
+ rows
39
+ lines=new Map
40
+ column=0
41
+ occupied=new Map
42
+ row=0
43
+ style={foreground:void 0,background:void 0,bold:!1,dim:!1,italic:!1,underline:!1,strikethrough:!1,inverse:!1,hidden:!1}
44
+ constructor(e,t){this.columns=e,this.rows=t}write(e){for(let t of f(e.toWellFormed())){if(this.row>=this.rows)break
45
+ if("sgr"===t.type)this.style=g(t.value,this.style)
46
+ else if("control"===t.type)this.control(t.value)
47
+ else for(let{segment:e}of s.segment(t.value)){if(this.row>=this.rows)break
48
+ this.paint(e)}}return this}control(e){if("\n"===e)this.nextLine()
49
+ else if("\r"===e)this.column=0
50
+ else if("\b"===e)this.column=Math.max(0,this.column-1)
51
+ else if("\t"===e){let e=8-this.column%8
52
+ for(let t=0;e>t&&this.row<this.rows;t++)this.paint(" ")}}nextLine(){this.row++,this.column=0,this.occupied.clear()}paint(e){let t=y(e,{ambiguousIsNarrow:!0})
53
+ if(0===t){let t=this.lines.get(this.row),i=this.occupied.get(this.column-1),o=void 0===i?void 0:t?.get(i)
54
+ return void(o&&o.column+o.width===this.column&&(o.text+=e))}if(t>this.columns&&(e="�",t=1),this.column+t>this.columns&&this.nextLine(),this.row>=this.rows)return
55
+ let i=this.lines.get(this.row)
56
+ i||(i=new Map,this.lines.set(this.row,i))
57
+ for(let e=this.column;e<this.column+t;e++){let t=this.occupied.get(e)
58
+ if(void 0===t)continue
59
+ let o=i.get(t)
60
+ i.delete(t)
61
+ for(let e=0;e<o.width;e++)this.occupied.delete(t+e)}let o={text:e,column:this.column,width:t,style:this.style}
62
+ i.set(this.column,o)
63
+ for(let e=0;t>e;e++)this.occupied.set(this.column+e,this.column)
64
+ this.column+=t}}try{let{values:n,positionals:d}=i({allowPositionals:!0,options:{output:{type:"string",short:"o"},columns:{type:"string"},rows:{type:"string"},padding:{type:"string"},"cell-width":{type:"string"},"cell-height":{type:"string"},title:{type:"string"},icon:{type:"string"},grid:{type:"boolean"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}})
65
+ if(n.help)process.stdout.write("vectorize-terminal — turn an ANSI transcript into an SVG\n\nUsage:\n vectorize-terminal [input.txt|-] [options]\n vectorize-terminal --title Build --rows 12 < build.log > build.svg\n\nInput defaults to stdin. Output defaults to stdout. No commands are executed.\n\nOptions:\n -o, --output FILE Write SVG to a file (or - for stdout)\n --columns N Terminal columns (default: 80)\n --rows N Visible rows (default: 24; excess content is clipped)\n --padding N Body padding (default: 30)\n --cell-width N Cell width (default: 45)\n --cell-height N Cell height (default: 100)\n --title TEXT Enable Windows Terminal decoration with this tab title\n --icon FILE Read a self-contained SVG tab icon; also enables decoration\n --grid Show the alignment grid\n -h, --help Show this help\n -v, --version Show the package version\n")
66
+ else if(n.version)process.stdout.write("0.1.0\n")
67
+ else{if(d.length>1)throw Error("Expected at most one input file")
68
+ let i=d[0]??"-"
69
+ if("-"===i&&process.stdin.isTTY)throw Error("Supply an input file or pipe a transcript to stdin. Use --help for usage.")
70
+ let a={content:e("-"===i?0:i,"utf8")}
71
+ for(let[e,t]of[["columns","columns"],["rows","rows"],["padding","padding"],["cell-width","cellWidth"],["cell-height","cellHeight"]]){let i=n[e]
72
+ if(void 0!==i){if(""===i.trim())throw Error(`--${e} needs a number`)
73
+ a[t]=Number(i)}}n.grid&&(a.grid=!0),void 0===n.title&&void 0===n.icon||(a.decoration={type:"windowsTerminal",tabTitle:n.title??"Terminal",...void 0===n.icon?{}:{tabIcon:e(n.icon,"utf8")}})
74
+ let c=function(e={}){let t=(e=>{if("string"==typeof e)return{content:e}
75
+ if("object"!=typeof e||null===e||Array.isArray(e))throw TypeError("Expected terminal content or an options object.")
76
+ let t=e
77
+ if(void 0!==t.content&&"string"!=typeof t.content)throw TypeError("content must be a string.")
78
+ for(let e of["columns","rows","padding","cellWidth","cellHeight"])if(void 0!==t[e]&&"number"!=typeof t[e])throw TypeError(`${e} must be a number.`)
79
+ let i=t.debug
80
+ if(void 0!==i&&("object"!=typeof i||null===i||Array.isArray(i)))throw TypeError("debug must be an object.")
81
+ for(let[e,o]of[["grid",t.grid],["debug.grid",i?.grid]])if(void 0!==o&&"boolean"!=typeof o)throw TypeError(`${e} must be a boolean.`)
82
+ return void 0!==t.decoration&&function(e){if("object"!=typeof e||null===e||!("type"in e)||"windowsTerminal"!==e.type)throw TypeError("decoration.type must be “windowsTerminal”.")
83
+ if(!("tabTitle"in e)||"string"!=typeof e.tabTitle)throw TypeError("decoration.tabTitle must be a string.")
84
+ if("tabIcon"in e&&void 0!==e.tabIcon&&("string"!=typeof e.tabIcon||!/^\s*(?:<\?xml[^?]*\?>\s*)?<svg[\s>]/u.test(e.tabIcon)))throw TypeError("decoration.tabIcon must be a complete SVG document, not a URL.")}(t.decoration),t})(e),i=function(e){let t=e.columns??80,i=e.rows??24,o=e.padding??30,n=e.cellWidth??45,r=e.cellHeight??100
85
+ for(let[e,o]of Object.entries({columns:t,rows:i}))if(!Number.isSafeInteger(o)||0>=o)throw RangeError(`${e} must be a positive safe integer.`)
86
+ for(let[e,t]of Object.entries({padding:o,cellWidth:n,cellHeight:r}))if("number"!=typeof t||!Number.isFinite(t)||("padding"===e?0>t:0>=t))throw RangeError(`${e} must be a finite ${"padding"===e?"nonnegative":"positive"} number.`)
87
+ if(n/45==0||.035*r==0)throw RangeError("Cell dimensions are too small to represent.")
88
+ let l=e.decoration?2*r:0,s=t*n,h=i*r,d=s+2*o,a=h+2*o+l
89
+ if(![d,a].every(e=>Number.isFinite(e)&&e<=Number.MAX_SAFE_INTEGER))throw RangeError("The resulting SVG dimensions exceed the safe numeric range.")
90
+ return{columns:t,rows:i,padding:o,cellWidth:n,cellHeight:r,width:d,height:a,headerHeight:l,contentWidth:s,contentHeight:h}}(t),n=t.debug?.grid??t.grid??!1
91
+ if(n&&i.columns+i.rows>1e5)throw RangeError("A debug grid may contain at most 100 000 rows and columns combined.")
92
+ let d=new b(i.columns,i.rows)
93
+ return d.write(t.content??""),`<svg xmlns="http://www.w3.org/2000/svg" width="${h(i.width)}" height="${h(i.height)}" viewBox="0 0 ${h(i.width)} ${h(i.height)}" role="img" aria-label="Terminal screenshot">\n<title>Terminal screenshot</title>\n<rect width="${h(i.width)}" height="${h(i.height)}" fill="${o}"/>\n${t.decoration?`${function(e,t){let i=Math.min(t.headerHeight/39,t.width/360),o=t.width/i,n=t.headerHeight/i-39
94
+ if(0>=i||!Number.isFinite(o)||!Number.isFinite(n))throw RangeError("Decoration geometry exceeds the representable numeric range.")
95
+ let r=Math.min(247,o-205),d=r-90,a=Math.max(1,Math.floor(d/7)),c=[...s.segment(p(e.tabTitle))].map(e=>e.segment),u=c.length>a?`${c.slice(0,a-1).join("")}…`:c.join(""),g=void 0===e.tabIcon?'<g transform="translate(16 13)"><path d="M1 1h5l2 2h7a1 1 0 0 1 1 1v10H0V2a1 1 0 0 1 1-1" fill="#d89e13"/><path d="M0 5h16l-1 9H0Z" fill="#fcd53f"/></g>':`<image x="16" y="12" width="16" height="16" preserveAspectRatio="xMidYMid meet" href="data:image/svg+xml;base64,${(e=>btoa(Array.from(m.encode(e),e=>String.fromCodePoint(e)).join("")))(e.tabIcon)}"/>`
96
+ return`<svg width="${h(t.width)}" height="${h(t.headerHeight)}" viewBox="0 0 ${h(t.width)} ${h(t.headerHeight)}" overflow="hidden" data-decoration="windowsTerminal">\n<rect width="${h(t.width)}" height="${h(t.headerHeight)}" fill="#2e2e2e"/>\n<g transform="scale(${h(i)}) translate(0 ${h(n)})">\n<path d="M0 39H${h(o)}" stroke="#292929"/>\n<path d="M0 39H2Q7 39 7 34V15Q7 7 15 7H${h(r-8)}Q${h(r)} 7 ${h(r)} 15V34Q${h(r)} 39 ${h(r+7)} 39Z" fill="#000000"/>\n${g}\n<svg x="40" y="7" width="${h(d)}" height="30" viewBox="0 0 ${h(d)} 30" overflow="hidden"><text x="0" y="19" fill="#ffffff" font-family="Segoe UI, sans-serif" font-size="12">${l(u)}</text></svg>\n<g fill="none" stroke-linecap="round" stroke-linejoin="round">\n<path d="M${h(r-26)} 17l6 6m0-6-6 6" stroke="#a0a0a0"/>\n<path d="M${h(r+18)} 20h8m-4-4v8" stroke="#cccccc"/>\n<path d="M${h(r+37)} 12v16" stroke="#454545"/>\n<path d="M${h(r+46)} 18l4 4 4-4" stroke="#cccccc"/>\n<g stroke="#ffffff">\n<path d="M${h(o-119)} 20h10"/>\n<rect x="${h(o-73)}" y="15" width="9" height="9"/>\n<path d="M${h(o-27)} 15l10 10m0-10-10 10"/>\n</g></g></g></svg>`}(t.decoration,i)}\n`:""}${function(e,t){let i=[],n=[],{cellWidth:l,cellHeight:s,padding:d,headerHeight:a}=t
97
+ for(let[t,c]of e)for(let e of $(c)){let c=d+e.column*l,u=a+d+t*s,g=e.width*l,[f,p]=r(e.style)
98
+ if(p!==o&&i.push(`<rect x="${h(c)}" y="${h(u)}" width="${h(g)}" height="${h(s)}" fill="${p}"/>`),e.style.hidden)continue
99
+ let m=e.style.dim?' opacity="0.5"':""
100
+ ""!==e.text.trim()&&n.push(`<text x="${h(c)}" y="${h(u+.76*s)}" textLength="${h(g)}" lengthAdjust="spacingAndGlyphs" fill="${f}"${e.style.bold?' font-weight="700"':""}${e.style.italic?' font-style="italic"':""}${m}>${w(e.text)}</text>`)
101
+ for(let[t,i,o]of[[e.style.underline,.89,"underline"],[e.style.strikethrough,.49,"strikethrough"]])t&&n.push(`<path data-text-decoration="${o}" d="M${h(c)} ${h(u+s*i)}h${h(g)}" stroke="${f}" stroke-width="${h(.035*s)}"${m}/>`)}return`<svg x="${h(d)}" y="${h(a+d)}" width="${h(t.contentWidth)}" height="${h(t.contentHeight)}" viewBox="${h(d)} ${h(a+d)} ${h(t.contentWidth)} ${h(t.contentHeight)}" overflow="hidden" data-terminal="true">\n<g data-layer="backgrounds">${i.join("")}</g>\n<g data-layer="text" font-family="JetBrains Mono, monospace" font-size="${h(.75*s)}" font-weight="400" font-variant-ligatures="none" style="font-feature-settings: 'liga' 0, 'calt' 0; white-space: pre" xml:space="preserve">${n.join("")}</g>\n</svg>`}(d.lines,i)}${n?`\n${function(e){let{padding:t,headerHeight:i,cellWidth:o,cellHeight:n,columns:r,rows:l,contentWidth:s,contentHeight:d}=e,a=[]
102
+ for(let n=0;r>=n;n++)a.push(`M${h(t+n*o)} ${h(i)}v${h(e.height-i)}`)
103
+ for(let o=0;l>=o;o++)a.push(`M0 ${h(i+t+o*n)}h${h(e.width)}`)
104
+ return`<svg x="0" y="${h(i)}" width="${h(e.width)}" height="${h(e.height-i)}" viewBox="0 ${h(i)} ${h(e.width)} ${h(e.height-i)}" overflow="hidden" data-grid="true"><path d="${a.join("")}" fill="none" stroke="#ffffff" stroke-opacity="0.13" stroke-width="${h(Math.min(o,n)/45)}"/><rect x="${h(t)}" y="${h(i+t)}" width="${h(s)}" height="${h(d)}" fill="none" stroke="#61d6d6" stroke-opacity="0.35" stroke-width="${h(Math.min(o,n)/45)}"/></svg>`}(i)}`:""}\n</svg>\n`}(a)
105
+ void 0===n.output||"-"===n.output?process.stdout.write(c):t(n.output,c,"utf8")}}catch(e){console.error(`vectorize-terminal: ${Error.isError(e)?e.message:String(e)}`),process.exitCode=1}
package/lib.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /** Render a deterministic, standalone terminal SVG. This function performs no I/O. */
2
+ export declare function vectorizeTerminal(input?: Options | string): string;
3
+ export type Options = {
4
+ /** @default 100 */
5
+ cellHeight?: number;
6
+ /** @default 45 */
7
+ cellWidth?: number;
8
+ /** @default 80 */
9
+ columns?: number;
10
+ /** Plain text or ANSI-styled output. No shell commands are executed. */
11
+ content?: string;
12
+ debug?: {
13
+ grid?: boolean;
14
+ };
15
+ /** Omitted by default. Adds exactly two cell heights above the body. */
16
+ decoration?: WindowsTerminalDecoration;
17
+ /** Shorthand for debug.grid. An explicit debug.grid takes precedence. */
18
+ grid?: boolean;
19
+ /** Padding on each side of the terminal body, in SVG units. @default 30 */
20
+ padding?: number;
21
+ /** @default 24 */
22
+ rows?: number;
23
+ };
24
+ export type WindowsTerminalDecoration = {
25
+ /** A complete SVG document, embedded as an isolated image (not inline markup). */
26
+ tabIcon?: string;
27
+ tabTitle: string;
28
+ type: "windowsTerminal";
29
+ };
30
+
31
+ export {
32
+ vectorizeTerminal as default,
33
+ };
34
+
35
+ export {};
package/lib.js ADDED
@@ -0,0 +1,92 @@
1
+ var e="#000000",t=()=>({foreground:void 0,background:void 0,bold:!1,dim:!1,italic:!1,underline:!1,strikethrough:!1,inverse:!1,hidden:!1})
2
+ function i(e){let t=e.foreground??"#cccccc",i=e.background??"#000000"
3
+ return e.inverse?[i,t]:[t,i]}function n(e){return e.toWellFormed().replaceAll(/[\u{0}-\u{8}\v\f\u{E}-\u{1F}\u{FFFE}\u{FFFF}]/gu,"�").replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&apos;")}var o=new Intl.Segmenter("en",{granularity:"grapheme"}),r=e=>String(Number(e.toPrecision(12))),l=["#0c0c0c","#c50f1f","#13a10e","#c19c00","#0037da","#881798","#3a96dd","#cccccc","#767676","#e74856","#16c60c","#f9f1a5","#3b78ff","#b4009e","#61d6d6","#f2f2f2"],s=e=>void 0!==e&&Number.isInteger(e)&&e>=0&&255>=e,h=e=>{if(3===e.length&&e.every(s))return`#${e.map(e=>e.toString(16).padStart(2,"0")).join("")}`}
4
+ function d(e){if(!s(e))return
5
+ if(16>e)return l[e]
6
+ if(e>=232){let t=8+10*(e-232)
7
+ return h([t,t,t])}let t=e-16,i=[0,95,135,175,215,255]
8
+ return h([i[Math.floor(t/36)],i[Math.floor(t/6)%6],i[t%6]])}function a(e,i){let n={...i},o=e.split(";")
9
+ for(let e=0;e<o.length;e++){let i=o[e].split(":"),r=Number(i[0]||0)
10
+ if([38,48,58].includes(r)){let t
11
+ if(i.length>1){let e=Number(i[1])
12
+ if(5===e&&3===i.length&&""!==i[2]&&(t=d(Number(i[2]))),2===e){let e=[]
13
+ 6!==i.length||""!==i[2]&&"0"!==i[2]?5===i.length&&(e=i.slice(2)):e=i.slice(3),e.every(e=>""!==e)&&(t=h(e.map(Number)))}}else{let i=Number(o[++e]),n={5:1,2:3}[i]??0,r=o.slice(e+1,e+1+n)
14
+ e+=n,r.length===n&&r.every(e=>/^\d+$/u.test(e))&&(5===i&&(t=d(Number(r[0]))),2===i&&(t=h(r.map(Number))))}t&&58!==r&&(n[38===r?"foreground":"background"]=t)
15
+ continue}i.length>1&&4!==r||(0===r?n=t():1===r?n.bold=!0:2===r?n.dim=!0:3===r?n.italic=!0:4===r?n.underline="0"!==i[1]:7===r?n.inverse=!0:8===r?n.hidden=!0:9===r?n.strikethrough=!0:22===r?(n.bold=!1,n.dim=!1):23===r?n.italic=!1:24===r?n.underline=!1:27===r?n.inverse=!1:28===r?n.hidden=!1:29===r?n.strikethrough=!1:39===r?n.foreground=void 0:49===r?n.background=void 0:30>r||r>37?40>r||r>47?90>r||r>97?100>r||r>107||(n.background=l[r-100+8]):n.foreground=l[r-90+8]:n.background=l[r-40]:n.foreground=l[r-30])}return n}function*c(e){let t=0
16
+ for(;t<e.length;){let i=t,n=e.charCodeAt(t)
17
+ if(n>=32&&(127>n||n>159)){do{t++}while(t<e.length&&!/[\u{0}-\u{1F}\u{7F}-\u{9F}]/u.test(e[t]))
18
+ yield{type:"text",value:e.slice(i,t)}
19
+ continue}if(t++,[8,9,10,13].includes(n)){yield{type:"control",value:e[i]}
20
+ continue}let o=n
21
+ if(27===n){if(t===e.length)break
22
+ o=e.charCodeAt(t++)}if(155===o||27===n&&91===o){let i=t
23
+ for(;t<e.length&&/[\u{30}-\u{3F}]/u.test(e[t]);)t++
24
+ let n=e.slice(i,t),o=t
25
+ for(;t<e.length&&/[\u{20}-\u{2F}]/u.test(e[t]);)t++
26
+ t<e.length&&/[\u{40}-\u{7E}]/u.test(e[t])&&("m"===e[t]&&t===o&&/^[\d:;]*$/u.test(n)&&(yield{type:"sgr",value:n}),t++)}else if([144,152,157,158,159].includes(o)||27===n&&[80,88,93,94,95].includes(o)){let i=157===o||93===o
27
+ for(;t<e.length;){if(156===e.charCodeAt(t)||i&&7===e.charCodeAt(t)){t++
28
+ break}if(""===e[t]&&"\\"===e[t+1]){t+=2
29
+ break}t++}}else if(27===n&&o>=32&&47>=o){for(;t<e.length&&/[\u{20}-\u{2F}]/u.test(e[t]);)t++
30
+ t<e.length&&/[\u{30}-\u{7E}]/u.test(e[t])&&t++}}}function u(e){return[...c(e)].filter(e=>"text"===e.type).map(e=>e.value).join("")}var f=new TextEncoder,g=e=>n(e).replaceAll(" ","&#xA0;"),m=function*(e){let t
31
+ for(let i of e.values().toArray().toSorted((e,t)=>e.column-t.column)){let e=JSON.stringify(i.style),n=/^[\u{20}-\u{7E}]+$/u.test(i.text)
32
+ t&&n&&/^[\u{20}-\u{7E}]+$/u.test(t.text)&&t.key===e&&t.column+t.width===i.column?(t.text+=i.text,t.width+=i.width):(t&&(yield t),t={...i,key:e})}t&&(yield t)}
33
+ import p from"string-width"
34
+ class w{columns
35
+ rows
36
+ lines=new Map
37
+ column=0
38
+ occupied=new Map
39
+ row=0
40
+ style={foreground:void 0,background:void 0,bold:!1,dim:!1,italic:!1,underline:!1,strikethrough:!1,inverse:!1,hidden:!1}
41
+ constructor(e,t){this.columns=e,this.rows=t}write(e){for(let t of c(e.toWellFormed())){if(this.row>=this.rows)break
42
+ if("sgr"===t.type)this.style=a(t.value,this.style)
43
+ else if("control"===t.type)this.control(t.value)
44
+ else for(let{segment:e}of o.segment(t.value)){if(this.row>=this.rows)break
45
+ this.paint(e)}}return this}control(e){if("\n"===e)this.nextLine()
46
+ else if("\r"===e)this.column=0
47
+ else if("\b"===e)this.column=Math.max(0,this.column-1)
48
+ else if("\t"===e){let e=8-this.column%8
49
+ for(let t=0;e>t&&this.row<this.rows;t++)this.paint(" ")}}nextLine(){this.row++,this.column=0,this.occupied.clear()}paint(e){let t=p(e,{ambiguousIsNarrow:!0})
50
+ if(0===t){let t=this.lines.get(this.row),i=this.occupied.get(this.column-1),n=void 0===i?void 0:t?.get(i)
51
+ return void(n&&n.column+n.width===this.column&&(n.text+=e))}if(t>this.columns&&(e="�",t=1),this.column+t>this.columns&&this.nextLine(),this.row>=this.rows)return
52
+ let i=this.lines.get(this.row)
53
+ i||(i=new Map,this.lines.set(this.row,i))
54
+ for(let e=this.column;e<this.column+t;e++){let t=this.occupied.get(e)
55
+ if(void 0===t)continue
56
+ let n=i.get(t)
57
+ i.delete(t)
58
+ for(let e=0;e<n.width;e++)this.occupied.delete(t+e)}let n={text:e,column:this.column,width:t,style:this.style}
59
+ i.set(this.column,n)
60
+ for(let e=0;t>e;e++)this.occupied.set(this.column+e,this.column)
61
+ this.column+=t}}function $(t={}){let l=(e=>{if("string"==typeof e)return{content:e}
62
+ if("object"!=typeof e||null===e||Array.isArray(e))throw TypeError("Expected terminal content or an options object.")
63
+ let t=e
64
+ if(void 0!==t.content&&"string"!=typeof t.content)throw TypeError("content must be a string.")
65
+ for(let e of["columns","rows","padding","cellWidth","cellHeight"])if(void 0!==t[e]&&"number"!=typeof t[e])throw TypeError(`${e} must be a number.`)
66
+ let i=t.debug
67
+ if(void 0!==i&&("object"!=typeof i||null===i||Array.isArray(i)))throw TypeError("debug must be an object.")
68
+ for(let[e,n]of[["grid",t.grid],["debug.grid",i?.grid]])if(void 0!==n&&"boolean"!=typeof n)throw TypeError(`${e} must be a boolean.`)
69
+ return void 0!==t.decoration&&function(e){if("object"!=typeof e||null===e||!("type"in e)||"windowsTerminal"!==e.type)throw TypeError("decoration.type must be “windowsTerminal”.")
70
+ if(!("tabTitle"in e)||"string"!=typeof e.tabTitle)throw TypeError("decoration.tabTitle must be a string.")
71
+ if("tabIcon"in e&&void 0!==e.tabIcon&&("string"!=typeof e.tabIcon||!/^\s*(?:<\?xml[^?]*\?>\s*)?<svg[\s>]/u.test(e.tabIcon)))throw TypeError("decoration.tabIcon must be a complete SVG document, not a URL.")}(t.decoration),t})(t),s=function(e){let t=e.columns??80,i=e.rows??24,n=e.padding??30,o=e.cellWidth??45,r=e.cellHeight??100
72
+ for(let[e,n]of Object.entries({columns:t,rows:i}))if(!Number.isSafeInteger(n)||0>=n)throw RangeError(`${e} must be a positive safe integer.`)
73
+ for(let[e,t]of Object.entries({padding:n,cellWidth:o,cellHeight:r}))if("number"!=typeof t||!Number.isFinite(t)||("padding"===e?0>t:0>=t))throw RangeError(`${e} must be a finite ${"padding"===e?"nonnegative":"positive"} number.`)
74
+ if(o/45==0||.035*r==0)throw RangeError("Cell dimensions are too small to represent.")
75
+ let l=e.decoration?2*r:0,s=t*o,h=i*r,d=s+2*n,a=h+2*n+l
76
+ if(![d,a].every(e=>Number.isFinite(e)&&e<=Number.MAX_SAFE_INTEGER))throw RangeError("The resulting SVG dimensions exceed the safe numeric range.")
77
+ return{columns:t,rows:i,padding:n,cellWidth:o,cellHeight:r,width:d,height:a,headerHeight:l,contentWidth:s,contentHeight:h}}(l),h=l.debug?.grid??l.grid??!1
78
+ if(h&&s.columns+s.rows>1e5)throw RangeError("A debug grid may contain at most 100 000 rows and columns combined.")
79
+ let d=new w(s.columns,s.rows)
80
+ return d.write(l.content??""),`<svg xmlns="http://www.w3.org/2000/svg" width="${r(s.width)}" height="${r(s.height)}" viewBox="0 0 ${r(s.width)} ${r(s.height)}" role="img" aria-label="Terminal screenshot">\n<title>Terminal screenshot</title>\n<rect width="${r(s.width)}" height="${r(s.height)}" fill="${e}"/>\n${l.decoration?`${function(e,t){let i=Math.min(t.headerHeight/39,t.width/360),l=t.width/i,s=t.headerHeight/i-39
81
+ if(0>=i||!Number.isFinite(l)||!Number.isFinite(s))throw RangeError("Decoration geometry exceeds the representable numeric range.")
82
+ let h=Math.min(247,l-205),d=h-90,a=Math.max(1,Math.floor(d/7)),c=[...o.segment(u(e.tabTitle))].map(e=>e.segment),g=c.length>a?`${c.slice(0,a-1).join("")}…`:c.join(""),m=void 0===e.tabIcon?'<g transform="translate(16 13)"><path d="M1 1h5l2 2h7a1 1 0 0 1 1 1v10H0V2a1 1 0 0 1 1-1" fill="#d89e13"/><path d="M0 5h16l-1 9H0Z" fill="#fcd53f"/></g>':`<image x="16" y="12" width="16" height="16" preserveAspectRatio="xMidYMid meet" href="data:image/svg+xml;base64,${(e=>btoa(Array.from(f.encode(e),e=>String.fromCodePoint(e)).join("")))(e.tabIcon)}"/>`
83
+ return`<svg width="${r(t.width)}" height="${r(t.headerHeight)}" viewBox="0 0 ${r(t.width)} ${r(t.headerHeight)}" overflow="hidden" data-decoration="windowsTerminal">\n<rect width="${r(t.width)}" height="${r(t.headerHeight)}" fill="#2e2e2e"/>\n<g transform="scale(${r(i)}) translate(0 ${r(s)})">\n<path d="M0 39H${r(l)}" stroke="#292929"/>\n<path d="M0 39H2Q7 39 7 34V15Q7 7 15 7H${r(h-8)}Q${r(h)} 7 ${r(h)} 15V34Q${r(h)} 39 ${r(h+7)} 39Z" fill="#000000"/>\n${m}\n<svg x="40" y="7" width="${r(d)}" height="30" viewBox="0 0 ${r(d)} 30" overflow="hidden"><text x="0" y="19" fill="#ffffff" font-family="Segoe UI, sans-serif" font-size="12">${n(g)}</text></svg>\n<g fill="none" stroke-linecap="round" stroke-linejoin="round">\n<path d="M${r(h-26)} 17l6 6m0-6-6 6" stroke="#a0a0a0"/>\n<path d="M${r(h+18)} 20h8m-4-4v8" stroke="#cccccc"/>\n<path d="M${r(h+37)} 12v16" stroke="#454545"/>\n<path d="M${r(h+46)} 18l4 4 4-4" stroke="#cccccc"/>\n<g stroke="#ffffff">\n<path d="M${r(l-119)} 20h10"/>\n<rect x="${r(l-73)}" y="15" width="9" height="9"/>\n<path d="M${r(l-27)} 15l10 10m0-10-10 10"/>\n</g></g></g></svg>`}(l.decoration,s)}\n`:""}${function(t,n){let o=[],l=[],{cellWidth:s,cellHeight:h,padding:d,headerHeight:a}=n
84
+ for(let[n,c]of t)for(let t of m(c)){let c=d+t.column*s,u=a+d+n*h,f=t.width*s,[m,p]=i(t.style)
85
+ if(p!==e&&o.push(`<rect x="${r(c)}" y="${r(u)}" width="${r(f)}" height="${r(h)}" fill="${p}"/>`),t.style.hidden)continue
86
+ let w=t.style.dim?' opacity="0.5"':""
87
+ ""!==t.text.trim()&&l.push(`<text x="${r(c)}" y="${r(u+.76*h)}" textLength="${r(f)}" lengthAdjust="spacingAndGlyphs" fill="${m}"${t.style.bold?' font-weight="700"':""}${t.style.italic?' font-style="italic"':""}${w}>${g(t.text)}</text>`)
88
+ for(let[e,i,n]of[[t.style.underline,.89,"underline"],[t.style.strikethrough,.49,"strikethrough"]])e&&l.push(`<path data-text-decoration="${n}" d="M${r(c)} ${r(u+h*i)}h${r(f)}" stroke="${m}" stroke-width="${r(.035*h)}"${w}/>`)}return`<svg x="${r(d)}" y="${r(a+d)}" width="${r(n.contentWidth)}" height="${r(n.contentHeight)}" viewBox="${r(d)} ${r(a+d)} ${r(n.contentWidth)} ${r(n.contentHeight)}" overflow="hidden" data-terminal="true">\n<g data-layer="backgrounds">${o.join("")}</g>\n<g data-layer="text" font-family="JetBrains Mono, monospace" font-size="${r(.75*h)}" font-weight="400" font-variant-ligatures="none" style="font-feature-settings: 'liga' 0, 'calt' 0; white-space: pre" xml:space="preserve">${l.join("")}</g>\n</svg>`}(d.lines,s)}${h?`\n${function(e){let{padding:t,headerHeight:i,cellWidth:n,cellHeight:o,columns:l,rows:s,contentWidth:h,contentHeight:d}=e,a=[]
89
+ for(let o=0;l>=o;o++)a.push(`M${r(t+o*n)} ${r(i)}v${r(e.height-i)}`)
90
+ for(let n=0;s>=n;n++)a.push(`M0 ${r(i+t+n*o)}h${r(e.width)}`)
91
+ return`<svg x="0" y="${r(i)}" width="${r(e.width)}" height="${r(e.height-i)}" viewBox="0 ${r(i)} ${r(e.width)} ${r(e.height-i)}" overflow="hidden" data-grid="true"><path d="${a.join("")}" fill="none" stroke="#ffffff" stroke-opacity="0.13" stroke-width="${r(Math.min(n,o)/45)}"/><rect x="${r(t)}" y="${r(i+t)}" width="${r(h)}" height="${r(d)}" fill="none" stroke="#61d6d6" stroke-opacity="0.35" stroke-width="${r(Math.min(n,o)/45)}"/></svg>`}(s)}`:""}\n</svg>\n`}var b=$
92
+ export{b as default,$ as vectorizeTerminal}
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"vectorize-terminal","description":"returns `'vectorize-terminal'`","version":"0.0.1","type":"module","exports":{".":{"types":"./index.d.ts","import":"./index.js","default":"./index.js"}},"author":"Jaid <6216144+Jaid@users.noreply.github.com> (https://github.com/Jaid)","license":"MIT","repository":"github:Jaid/vectorize-terminal"}
1
+ {"bugs":{"url":"https://github.com/Jaid/vectorize-terminal/issues"},"dependencies":{"string-width":"^8.2.2"},"description":"Renders ANSI terminal transcripts to SVG terminal screenshots","homepage":"https://github.com/Jaid/vectorize-terminal#readme","keywords":["ansi","cli","screenshot","svg","terminal","typescript"],"license":"MIT","name":"vectorize-terminal","repository":"github:Jaid/vectorize-terminal","version":"0.1.0","type":"module","exports":{".":{"types":"./lib.d.ts","import":"./lib.js","default":"./lib.js"}},"types":"./lib.d.ts","bin":{"vectorize-terminal":"./cli.js"}}
package/index.d.ts DELETED
@@ -1,6 +0,0 @@
1
- declare module 'vectorize-terminal' {
2
- /**
3
- * returns `'vectorize-terminal'`
4
- */
5
- export default () => string
6
- }
package/index.js DELETED
@@ -1 +0,0 @@
1
- export default () => 'vectorize-terminal'
package/readme.md DELETED
@@ -1 +0,0 @@
1
- # vectorize-terminal
File without changes