@oh-my-pi/pi-utils 16.0.7 → 16.0.9
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/CHANGELOG.md +10 -0
- package/dist/types/mermaid-ascii.d.ts +1 -1
- package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
- package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
- package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
- package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
- package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
- package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
- package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
- package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
- package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
- package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
- package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
- package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
- package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
- package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
- package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
- package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
- package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
- package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
- package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
- package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
- package/package.json +2 -3
- package/src/mermaid-ascii.ts +1 -1
- package/src/vendor/mermaid-ascii/NOTICE +33 -0
- package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
- package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
- package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
- package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
- package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
- package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
- package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
- package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
- package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
- package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
- package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
- package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +215 -0
- package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
- package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
- package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
- package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
- package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
- package/src/vendor/mermaid-ascii/class/types.ts +121 -0
- package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
- package/src/vendor/mermaid-ascii/er/types.ts +91 -0
- package/src/vendor/mermaid-ascii/index.ts +14 -0
- package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
- package/src/vendor/mermaid-ascii/parser.ts +645 -0
- package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
- package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
- package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
- package/src/vendor/mermaid-ascii/types.ts +164 -0
- package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
- package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
- package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ASCII renderer — color utilities
|
|
3
|
+
//
|
|
4
|
+
// Provides color output for themed ASCII diagrams.
|
|
5
|
+
// Supports ANSI terminal modes (16/256/truecolor) and HTML <span> tags
|
|
6
|
+
// for browser rendering.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
import type { CharRole, AsciiTheme, ColorMode } from './types'
|
|
10
|
+
|
|
11
|
+
declare const document: unknown
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Default theme — matches SVG theme colors for consistency
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default ASCII theme derived from the SVG renderer's color palette.
|
|
19
|
+
* Uses the same mixing ratios to maintain visual consistency.
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_ASCII_THEME: AsciiTheme = {
|
|
22
|
+
fg: '#27272a', // zinc-800 — primary text
|
|
23
|
+
border: '#a1a1aa', // zinc-400 — node borders (12% mix)
|
|
24
|
+
line: '#71717a', // zinc-500 — edge lines (35% mix)
|
|
25
|
+
arrow: '#52525b', // zinc-600 — arrowheads (60% mix)
|
|
26
|
+
corner: '#71717a', // same as line
|
|
27
|
+
junction: '#a1a1aa', // same as border
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Color mode detection
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Detect the best color mode for the current environment.
|
|
36
|
+
*
|
|
37
|
+
* Terminal detection order:
|
|
38
|
+
* 1. COLORTERM=truecolor or COLORTERM=24bit → truecolor
|
|
39
|
+
* 2. TERM contains "256color" → ansi256
|
|
40
|
+
* 3. TERM is set and not "dumb" → ansi16
|
|
41
|
+
*
|
|
42
|
+
* Browser: returns 'html' (uses <span> tags with inline styles).
|
|
43
|
+
* Unknown/piped: returns 'none'.
|
|
44
|
+
*/
|
|
45
|
+
export function detectColorMode(): ColorMode {
|
|
46
|
+
// Check if we're in a Node.js-like environment with process object
|
|
47
|
+
// Use globalThis to safely check for process without TypeScript errors
|
|
48
|
+
const proc = (globalThis as { process?: { stdout?: { isTTY?: boolean }, env?: Record<string, string | undefined> } }).process
|
|
49
|
+
|
|
50
|
+
if (proc) {
|
|
51
|
+
// Check if stdout is a TTY (not piped/redirected)
|
|
52
|
+
if (!proc.stdout?.isTTY) {
|
|
53
|
+
return 'none'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const colorTerm = proc.env?.COLORTERM?.toLowerCase() ?? ''
|
|
57
|
+
const term = proc.env?.TERM?.toLowerCase() ?? ''
|
|
58
|
+
|
|
59
|
+
// True color support
|
|
60
|
+
if (colorTerm === 'truecolor' || colorTerm === '24bit') {
|
|
61
|
+
return 'truecolor'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 256 color support
|
|
65
|
+
if (term.includes('256color') || term.includes('256')) {
|
|
66
|
+
return 'ansi256'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Basic color support
|
|
70
|
+
if (term && term !== 'dumb') {
|
|
71
|
+
return 'ansi16'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return 'none'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// No process object → browser environment → use HTML color output
|
|
78
|
+
if (typeof document !== 'undefined') {
|
|
79
|
+
return 'html'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return 'none'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Hex color parsing
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Parse a hex color string to RGB values.
|
|
91
|
+
* Supports both 3-char (#RGB) and 6-char (#RRGGBB) formats.
|
|
92
|
+
*/
|
|
93
|
+
function parseHex(hex: string): { r: number; g: number; b: number } {
|
|
94
|
+
const h = hex.replace('#', '')
|
|
95
|
+
if (h.length === 3) {
|
|
96
|
+
return {
|
|
97
|
+
r: parseInt(h[0]! + h[0]!, 16),
|
|
98
|
+
g: parseInt(h[1]! + h[1]!, 16),
|
|
99
|
+
b: parseInt(h[2]! + h[2]!, 16),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
r: parseInt(h.substring(0, 2), 16),
|
|
104
|
+
g: parseInt(h.substring(2, 4), 16),
|
|
105
|
+
b: parseInt(h.substring(4, 6), 16),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// ANSI escape code generation
|
|
111
|
+
// ============================================================================
|
|
112
|
+
|
|
113
|
+
/** ANSI escape sequence prefix */
|
|
114
|
+
const ESC = '\x1b['
|
|
115
|
+
/** Reset all attributes */
|
|
116
|
+
const RESET = `${ESC}0m`
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Generate ANSI foreground color escape sequence for 24-bit true color.
|
|
120
|
+
* Format: ESC[38;2;R;G;Bm
|
|
121
|
+
*/
|
|
122
|
+
function truecolorFg(hex: string): string {
|
|
123
|
+
const { r, g, b } = parseHex(hex)
|
|
124
|
+
return `${ESC}38;2;${r};${g};${b}m`
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Find the closest 256-color palette index for an RGB color.
|
|
129
|
+
* The 256-color palette has:
|
|
130
|
+
* - 0-15: Standard colors (duplicates of 16-color)
|
|
131
|
+
* - 16-231: 6x6x6 color cube (216 colors)
|
|
132
|
+
* - 232-255: Grayscale ramp (24 shades)
|
|
133
|
+
*/
|
|
134
|
+
function rgbTo256(r: number, g: number, b: number): number {
|
|
135
|
+
// Check if it's close to grayscale
|
|
136
|
+
const avg = (r + g + b) / 3
|
|
137
|
+
const maxDiff = Math.max(Math.abs(r - avg), Math.abs(g - avg), Math.abs(b - avg))
|
|
138
|
+
|
|
139
|
+
if (maxDiff < 10) {
|
|
140
|
+
// Use grayscale ramp (232-255)
|
|
141
|
+
// Each step is ~10.625 (256/24)
|
|
142
|
+
const gray = Math.round((avg / 255) * 23)
|
|
143
|
+
return 232 + Math.min(23, Math.max(0, gray))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Use 6x6x6 color cube (16-231)
|
|
147
|
+
// Each channel maps to 0-5: 0, 95, 135, 175, 215, 255
|
|
148
|
+
const toIndex = (v: number): number => {
|
|
149
|
+
if (v < 48) return 0
|
|
150
|
+
if (v < 115) return 1
|
|
151
|
+
return Math.min(5, Math.floor((v - 35) / 40))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const ri = toIndex(r)
|
|
155
|
+
const gi = toIndex(g)
|
|
156
|
+
const bi = toIndex(b)
|
|
157
|
+
|
|
158
|
+
return 16 + (36 * ri) + (6 * gi) + bi
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Generate ANSI foreground color escape sequence for 256-color mode.
|
|
163
|
+
* Format: ESC[38;5;Nm
|
|
164
|
+
*/
|
|
165
|
+
function ansi256Fg(hex: string): string {
|
|
166
|
+
const { r, g, b } = parseHex(hex)
|
|
167
|
+
const index = rgbTo256(r, g, b)
|
|
168
|
+
return `${ESC}38;5;${index}m`
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Map an RGB color to the closest 16-color ANSI code.
|
|
173
|
+
* Returns the foreground color escape sequence.
|
|
174
|
+
*
|
|
175
|
+
* Standard 16 colors:
|
|
176
|
+
* 0=black, 1=red, 2=green, 3=yellow, 4=blue, 5=magenta, 6=cyan, 7=white
|
|
177
|
+
* 8-15 = bright versions
|
|
178
|
+
*/
|
|
179
|
+
function ansi16Fg(hex: string): string {
|
|
180
|
+
const { r, g, b } = parseHex(hex)
|
|
181
|
+
const luma = 0.299 * r + 0.587 * g + 0.114 * b
|
|
182
|
+
|
|
183
|
+
// Determine brightness (use bright colors for better visibility)
|
|
184
|
+
const bright = luma > 100 ? 0 : 60 // 60 = bright variant offset
|
|
185
|
+
|
|
186
|
+
// Determine base color based on dominant channel
|
|
187
|
+
let code: number
|
|
188
|
+
if (r > 180 && g < 100 && b < 100) code = 31 // red
|
|
189
|
+
else if (g > 180 && r < 100 && b < 100) code = 32 // green
|
|
190
|
+
else if (r > 150 && g > 150 && b < 100) code = 33 // yellow
|
|
191
|
+
else if (b > 180 && r < 100 && g < 100) code = 34 // blue
|
|
192
|
+
else if (r > 150 && b > 150 && g < 100) code = 35 // magenta
|
|
193
|
+
else if (g > 150 && b > 150 && r < 100) code = 36 // cyan
|
|
194
|
+
else if (luma > 200) code = 37 // white
|
|
195
|
+
else if (luma < 50) code = 30 // black
|
|
196
|
+
else code = 37 // default to white for grays
|
|
197
|
+
|
|
198
|
+
return `${ESC}${code + bright}m`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// HTML color output (for browser rendering)
|
|
203
|
+
// ============================================================================
|
|
204
|
+
|
|
205
|
+
/** Escape characters that would break HTML output. */
|
|
206
|
+
function escapeHtml(text: string): string {
|
|
207
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Wrap text in a <span> with an inline color style. */
|
|
211
|
+
function htmlSpan(hex: string, text: string): string {
|
|
212
|
+
return `<span style="color:${hex}">${escapeHtml(text)}</span>`
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ============================================================================
|
|
216
|
+
// Role → color mapping
|
|
217
|
+
// ============================================================================
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get the color for a character role from the theme.
|
|
221
|
+
*/
|
|
222
|
+
function getRoleColor(role: CharRole, theme: AsciiTheme): string {
|
|
223
|
+
switch (role) {
|
|
224
|
+
case 'text': return theme.fg
|
|
225
|
+
case 'border': return theme.border
|
|
226
|
+
case 'line': return theme.line
|
|
227
|
+
case 'arrow': return theme.arrow
|
|
228
|
+
case 'corner': return theme.corner ?? theme.line
|
|
229
|
+
case 'junction': return theme.junction ?? theme.border
|
|
230
|
+
default: return theme.fg
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Generate the ANSI escape sequence for a role color.
|
|
236
|
+
*/
|
|
237
|
+
export function getAnsiColor(role: CharRole, theme: AsciiTheme, mode: ColorMode): string {
|
|
238
|
+
if (mode === 'none') return ''
|
|
239
|
+
|
|
240
|
+
const hex = getRoleColor(role, theme)
|
|
241
|
+
|
|
242
|
+
switch (mode) {
|
|
243
|
+
case 'truecolor': return truecolorFg(hex)
|
|
244
|
+
case 'ansi256': return ansi256Fg(hex)
|
|
245
|
+
case 'ansi16': return ansi16Fg(hex)
|
|
246
|
+
default: return ''
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get the ANSI reset sequence.
|
|
252
|
+
*/
|
|
253
|
+
export function getAnsiReset(mode: ColorMode): string {
|
|
254
|
+
return mode === 'none' ? '' : RESET
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Wrap a character with ANSI color codes based on its role.
|
|
259
|
+
*/
|
|
260
|
+
export function colorizeChar(
|
|
261
|
+
char: string,
|
|
262
|
+
role: CharRole | null,
|
|
263
|
+
theme: AsciiTheme,
|
|
264
|
+
mode: ColorMode,
|
|
265
|
+
): string {
|
|
266
|
+
if (mode === 'none' || role === null || char === ' ') {
|
|
267
|
+
return char
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const colorCode = getAnsiColor(role, theme, mode)
|
|
271
|
+
return `${colorCode}${char}${RESET}`
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Colorize an entire line efficiently by grouping consecutive same-role characters.
|
|
276
|
+
* This reduces the number of escape sequences (ANSI) or span tags (HTML) in the output.
|
|
277
|
+
*/
|
|
278
|
+
export function colorizeLine(
|
|
279
|
+
chars: string[],
|
|
280
|
+
roles: (CharRole | null)[],
|
|
281
|
+
theme: AsciiTheme,
|
|
282
|
+
mode: ColorMode,
|
|
283
|
+
): string {
|
|
284
|
+
if (mode === 'none') {
|
|
285
|
+
return chars.join('')
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (mode === 'html') {
|
|
289
|
+
return colorizeLineHtml(chars, roles, theme)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let result = ''
|
|
293
|
+
let currentRole: CharRole | null = null
|
|
294
|
+
let buffer = ''
|
|
295
|
+
|
|
296
|
+
for (let i = 0; i < chars.length; i++) {
|
|
297
|
+
const char = chars[i]!
|
|
298
|
+
const role = roles[i] ?? null
|
|
299
|
+
|
|
300
|
+
// Whitespace doesn't need coloring
|
|
301
|
+
if (char === ' ') {
|
|
302
|
+
// Flush any buffered characters (with or without color)
|
|
303
|
+
if (buffer.length > 0) {
|
|
304
|
+
if (currentRole !== null) {
|
|
305
|
+
result += getAnsiColor(currentRole, theme, mode) + buffer + RESET
|
|
306
|
+
} else {
|
|
307
|
+
result += buffer
|
|
308
|
+
}
|
|
309
|
+
buffer = ''
|
|
310
|
+
currentRole = null
|
|
311
|
+
}
|
|
312
|
+
result += char
|
|
313
|
+
continue
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Same role as previous — accumulate
|
|
317
|
+
if (role === currentRole) {
|
|
318
|
+
buffer += char
|
|
319
|
+
continue
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Role changed — flush buffer (with or without color) and start new
|
|
323
|
+
if (buffer.length > 0) {
|
|
324
|
+
if (currentRole !== null) {
|
|
325
|
+
result += getAnsiColor(currentRole, theme, mode) + buffer + RESET
|
|
326
|
+
} else {
|
|
327
|
+
result += buffer
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
buffer = char
|
|
331
|
+
currentRole = role
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Flush remaining buffer
|
|
335
|
+
if (buffer.length > 0 && currentRole !== null) {
|
|
336
|
+
result += getAnsiColor(currentRole, theme, mode) + buffer + RESET
|
|
337
|
+
} else if (buffer.length > 0) {
|
|
338
|
+
result += buffer
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return result
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* HTML-specific line colorization.
|
|
346
|
+
* Groups consecutive same-role characters into <span> tags with inline color styles.
|
|
347
|
+
* Whitespace is emitted bare (no wrapping) to keep output compact.
|
|
348
|
+
*/
|
|
349
|
+
function colorizeLineHtml(
|
|
350
|
+
chars: string[],
|
|
351
|
+
roles: (CharRole | null)[],
|
|
352
|
+
theme: AsciiTheme,
|
|
353
|
+
): string {
|
|
354
|
+
let result = ''
|
|
355
|
+
let currentRole: CharRole | null = null
|
|
356
|
+
let buffer = ''
|
|
357
|
+
|
|
358
|
+
const flush = () => {
|
|
359
|
+
if (buffer.length === 0) return
|
|
360
|
+
if (currentRole !== null) {
|
|
361
|
+
result += htmlSpan(getRoleColor(currentRole, theme), buffer)
|
|
362
|
+
} else {
|
|
363
|
+
result += escapeHtml(buffer)
|
|
364
|
+
}
|
|
365
|
+
buffer = ''
|
|
366
|
+
currentRole = null
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
for (let i = 0; i < chars.length; i++) {
|
|
370
|
+
const char = chars[i]!
|
|
371
|
+
const role = roles[i] ?? null
|
|
372
|
+
|
|
373
|
+
if (char === ' ') {
|
|
374
|
+
flush()
|
|
375
|
+
result += ' '
|
|
376
|
+
continue
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (role === currentRole) {
|
|
380
|
+
buffer += char
|
|
381
|
+
continue
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
flush()
|
|
385
|
+
buffer = char
|
|
386
|
+
currentRole = role
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
flush()
|
|
390
|
+
return result
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Colorize a text string with a direct hex color.
|
|
395
|
+
* Used by renderers that need per-cell color control (e.g. multi-series xychart).
|
|
396
|
+
* Handles all output modes: ANSI (16/256/truecolor) and HTML.
|
|
397
|
+
*/
|
|
398
|
+
export function colorizeText(text: string, hex: string, mode: ColorMode): string {
|
|
399
|
+
if (mode === 'none' || text.length === 0) return text
|
|
400
|
+
if (mode === 'html') return htmlSpan(hex, text)
|
|
401
|
+
let code: string
|
|
402
|
+
switch (mode) {
|
|
403
|
+
case 'truecolor': code = truecolorFg(hex); break
|
|
404
|
+
case 'ansi256': code = ansi256Fg(hex); break
|
|
405
|
+
case 'ansi16': code = ansi16Fg(hex); break
|
|
406
|
+
default: return text
|
|
407
|
+
}
|
|
408
|
+
return `${code}${text}${RESET}`
|
|
409
|
+
}
|