1package colorable
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7	"math"
8	"os"
9	"strconv"
10	"strings"
11	"syscall"
12	"unsafe"
13
14	"github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty"
15)
16
17const (
18	foregroundBlue      = 0x1
19	foregroundGreen     = 0x2
20	foregroundRed       = 0x4
21	foregroundIntensity = 0x8
22	foregroundMask      = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
23	backgroundBlue      = 0x10
24	backgroundGreen     = 0x20
25	backgroundRed       = 0x40
26	backgroundIntensity = 0x80
27	backgroundMask      = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
28)
29
30type wchar uint16
31type short int16
32type dword uint32
33type word uint16
34
35type coord struct {
36	x short
37	y short
38}
39
40type smallRect struct {
41	left   short
42	top    short
43	right  short
44	bottom short
45}
46
47type consoleScreenBufferInfo struct {
48	size              coord
49	cursorPosition    coord
50	attributes        word
51	window            smallRect
52	maximumWindowSize coord
53}
54
55var (
56	kernel32                       = syscall.NewLazyDLL("kernel32.dll")
57	procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
58	procSetConsoleTextAttribute    = kernel32.NewProc("SetConsoleTextAttribute")
59	procSetConsoleCursorPosition   = kernel32.NewProc("SetConsoleCursorPosition")
60	procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
61	procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
62)
63
64type Writer struct {
65	out     io.Writer
66	handle  syscall.Handle
67	lastbuf bytes.Buffer
68	oldattr word
69}
70
71func NewColorable(file *os.File) io.Writer {
72	if file == nil {
73		panic("nil passed instead of *os.File to NewColorable()")
74	}
75
76	if isatty.IsTerminal(file.Fd()) {
77		var csbi consoleScreenBufferInfo
78		handle := syscall.Handle(file.Fd())
79		procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
80		return &Writer{out: file, handle: handle, oldattr: csbi.attributes}
81	} else {
82		return file
83	}
84}
85
86func NewColorableStdout() io.Writer {
87	return NewColorable(os.Stdout)
88}
89
90func NewColorableStderr() io.Writer {
91	return NewColorable(os.Stderr)
92}
93
94var color256 = map[int]int{
95	0:   0x000000,
96	1:   0x800000,
97	2:   0x008000,
98	3:   0x808000,
99	4:   0x000080,
100	5:   0x800080,
101	6:   0x008080,
102	7:   0xc0c0c0,
103	8:   0x808080,
104	9:   0xff0000,
105	10:  0x00ff00,
106	11:  0xffff00,
107	12:  0x0000ff,
108	13:  0xff00ff,
109	14:  0x00ffff,
110	15:  0xffffff,
111	16:  0x000000,
112	17:  0x00005f,
113	18:  0x000087,
114	19:  0x0000af,
115	20:  0x0000d7,
116	21:  0x0000ff,
117	22:  0x005f00,
118	23:  0x005f5f,
119	24:  0x005f87,
120	25:  0x005faf,
121	26:  0x005fd7,
122	27:  0x005fff,
123	28:  0x008700,
124	29:  0x00875f,
125	30:  0x008787,
126	31:  0x0087af,
127	32:  0x0087d7,
128	33:  0x0087ff,
129	34:  0x00af00,
130	35:  0x00af5f,
131	36:  0x00af87,
132	37:  0x00afaf,
133	38:  0x00afd7,
134	39:  0x00afff,
135	40:  0x00d700,
136	41:  0x00d75f,
137	42:  0x00d787,
138	43:  0x00d7af,
139	44:  0x00d7d7,
140	45:  0x00d7ff,
141	46:  0x00ff00,
142	47:  0x00ff5f,
143	48:  0x00ff87,
144	49:  0x00ffaf,
145	50:  0x00ffd7,
146	51:  0x00ffff,
147	52:  0x5f0000,
148	53:  0x5f005f,
149	54:  0x5f0087,
150	55:  0x5f00af,
151	56:  0x5f00d7,
152	57:  0x5f00ff,
153	58:  0x5f5f00,
154	59:  0x5f5f5f,
155	60:  0x5f5f87,
156	61:  0x5f5faf,
157	62:  0x5f5fd7,
158	63:  0x5f5fff,
159	64:  0x5f8700,
160	65:  0x5f875f,
161	66:  0x5f8787,
162	67:  0x5f87af,
163	68:  0x5f87d7,
164	69:  0x5f87ff,
165	70:  0x5faf00,
166	71:  0x5faf5f,
167	72:  0x5faf87,
168	73:  0x5fafaf,
169	74:  0x5fafd7,
170	75:  0x5fafff,
171	76:  0x5fd700,
172	77:  0x5fd75f,
173	78:  0x5fd787,
174	79:  0x5fd7af,
175	80:  0x5fd7d7,
176	81:  0x5fd7ff,
177	82:  0x5fff00,
178	83:  0x5fff5f,
179	84:  0x5fff87,
180	85:  0x5fffaf,
181	86:  0x5fffd7,
182	87:  0x5fffff,
183	88:  0x870000,
184	89:  0x87005f,
185	90:  0x870087,
186	91:  0x8700af,
187	92:  0x8700d7,
188	93:  0x8700ff,
189	94:  0x875f00,
190	95:  0x875f5f,
191	96:  0x875f87,
192	97:  0x875faf,
193	98:  0x875fd7,
194	99:  0x875fff,
195	100: 0x878700,
196	101: 0x87875f,
197	102: 0x878787,
198	103: 0x8787af,
199	104: 0x8787d7,
200	105: 0x8787ff,
201	106: 0x87af00,
202	107: 0x87af5f,
203	108: 0x87af87,
204	109: 0x87afaf,
205	110: 0x87afd7,
206	111: 0x87afff,
207	112: 0x87d700,
208	113: 0x87d75f,
209	114: 0x87d787,
210	115: 0x87d7af,
211	116: 0x87d7d7,
212	117: 0x87d7ff,
213	118: 0x87ff00,
214	119: 0x87ff5f,
215	120: 0x87ff87,
216	121: 0x87ffaf,
217	122: 0x87ffd7,
218	123: 0x87ffff,
219	124: 0xaf0000,
220	125: 0xaf005f,
221	126: 0xaf0087,
222	127: 0xaf00af,
223	128: 0xaf00d7,
224	129: 0xaf00ff,
225	130: 0xaf5f00,
226	131: 0xaf5f5f,
227	132: 0xaf5f87,
228	133: 0xaf5faf,
229	134: 0xaf5fd7,
230	135: 0xaf5fff,
231	136: 0xaf8700,
232	137: 0xaf875f,
233	138: 0xaf8787,
234	139: 0xaf87af,
235	140: 0xaf87d7,
236	141: 0xaf87ff,
237	142: 0xafaf00,
238	143: 0xafaf5f,
239	144: 0xafaf87,
240	145: 0xafafaf,
241	146: 0xafafd7,
242	147: 0xafafff,
243	148: 0xafd700,
244	149: 0xafd75f,
245	150: 0xafd787,
246	151: 0xafd7af,
247	152: 0xafd7d7,
248	153: 0xafd7ff,
249	154: 0xafff00,
250	155: 0xafff5f,
251	156: 0xafff87,
252	157: 0xafffaf,
253	158: 0xafffd7,
254	159: 0xafffff,
255	160: 0xd70000,
256	161: 0xd7005f,
257	162: 0xd70087,
258	163: 0xd700af,
259	164: 0xd700d7,
260	165: 0xd700ff,
261	166: 0xd75f00,
262	167: 0xd75f5f,
263	168: 0xd75f87,
264	169: 0xd75faf,
265	170: 0xd75fd7,
266	171: 0xd75fff,
267	172: 0xd78700,
268	173: 0xd7875f,
269	174: 0xd78787,
270	175: 0xd787af,
271	176: 0xd787d7,
272	177: 0xd787ff,
273	178: 0xd7af00,
274	179: 0xd7af5f,
275	180: 0xd7af87,
276	181: 0xd7afaf,
277	182: 0xd7afd7,
278	183: 0xd7afff,
279	184: 0xd7d700,
280	185: 0xd7d75f,
281	186: 0xd7d787,
282	187: 0xd7d7af,
283	188: 0xd7d7d7,
284	189: 0xd7d7ff,
285	190: 0xd7ff00,
286	191: 0xd7ff5f,
287	192: 0xd7ff87,
288	193: 0xd7ffaf,
289	194: 0xd7ffd7,
290	195: 0xd7ffff,
291	196: 0xff0000,
292	197: 0xff005f,
293	198: 0xff0087,
294	199: 0xff00af,
295	200: 0xff00d7,
296	201: 0xff00ff,
297	202: 0xff5f00,
298	203: 0xff5f5f,
299	204: 0xff5f87,
300	205: 0xff5faf,
301	206: 0xff5fd7,
302	207: 0xff5fff,
303	208: 0xff8700,
304	209: 0xff875f,
305	210: 0xff8787,
306	211: 0xff87af,
307	212: 0xff87d7,
308	213: 0xff87ff,
309	214: 0xffaf00,
310	215: 0xffaf5f,
311	216: 0xffaf87,
312	217: 0xffafaf,
313	218: 0xffafd7,
314	219: 0xffafff,
315	220: 0xffd700,
316	221: 0xffd75f,
317	222: 0xffd787,
318	223: 0xffd7af,
319	224: 0xffd7d7,
320	225: 0xffd7ff,
321	226: 0xffff00,
322	227: 0xffff5f,
323	228: 0xffff87,
324	229: 0xffffaf,
325	230: 0xffffd7,
326	231: 0xffffff,
327	232: 0x080808,
328	233: 0x121212,
329	234: 0x1c1c1c,
330	235: 0x262626,
331	236: 0x303030,
332	237: 0x3a3a3a,
333	238: 0x444444,
334	239: 0x4e4e4e,
335	240: 0x585858,
336	241: 0x626262,
337	242: 0x6c6c6c,
338	243: 0x767676,
339	244: 0x808080,
340	245: 0x8a8a8a,
341	246: 0x949494,
342	247: 0x9e9e9e,
343	248: 0xa8a8a8,
344	249: 0xb2b2b2,
345	250: 0xbcbcbc,
346	251: 0xc6c6c6,
347	252: 0xd0d0d0,
348	253: 0xdadada,
349	254: 0xe4e4e4,
350	255: 0xeeeeee,
351}
352
353func (w *Writer) Write(data []byte) (n int, err error) {
354	var csbi consoleScreenBufferInfo
355	procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
356
357	er := bytes.NewBuffer(data)
358loop:
359	for {
360		r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
361		if r1 == 0 {
362			break loop
363		}
364
365		c1, _, err := er.ReadRune()
366		if err != nil {
367			break loop
368		}
369		if c1 != 0x1b {
370			fmt.Fprint(w.out, string(c1))
371			continue
372		}
373		c2, _, err := er.ReadRune()
374		if err != nil {
375			w.lastbuf.WriteRune(c1)
376			break loop
377		}
378		if c2 != 0x5b {
379			w.lastbuf.WriteRune(c1)
380			w.lastbuf.WriteRune(c2)
381			continue
382		}
383
384		var buf bytes.Buffer
385		var m rune
386		for {
387			c, _, err := er.ReadRune()
388			if err != nil {
389				w.lastbuf.WriteRune(c1)
390				w.lastbuf.WriteRune(c2)
391				w.lastbuf.Write(buf.Bytes())
392				break loop
393			}
394			if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
395				m = c
396				break
397			}
398			buf.Write([]byte(string(c)))
399		}
400
401		var csbi consoleScreenBufferInfo
402		switch m {
403		case 'A':
404			n, err = strconv.Atoi(buf.String())
405			if err != nil {
406				continue
407			}
408			procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
409			csbi.cursorPosition.y -= short(n)
410			procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
411		case 'B':
412			n, err = strconv.Atoi(buf.String())
413			if err != nil {
414				continue
415			}
416			procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
417			csbi.cursorPosition.y += short(n)
418			procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
419		case 'C':
420			n, err = strconv.Atoi(buf.String())
421			if err != nil {
422				continue
423			}
424			procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
425			csbi.cursorPosition.x -= short(n)
426			procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
427		case 'D':
428			n, err = strconv.Atoi(buf.String())
429			if err != nil {
430				continue
431			}
432			if n, err = strconv.Atoi(buf.String()); err == nil {
433				var csbi consoleScreenBufferInfo
434				procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
435				csbi.cursorPosition.x += short(n)
436				procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
437			}
438		case 'E':
439			n, err = strconv.Atoi(buf.String())
440			if err != nil {
441				continue
442			}
443			procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
444			csbi.cursorPosition.x = 0
445			csbi.cursorPosition.y += short(n)
446			procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
447		case 'F':
448			n, err = strconv.Atoi(buf.String())
449			if err != nil {
450				continue
451			}
452			procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
453			csbi.cursorPosition.x = 0
454			csbi.cursorPosition.y -= short(n)
455			procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
456		case 'G':
457			n, err = strconv.Atoi(buf.String())
458			if err != nil {
459				continue
460			}
461			procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
462			csbi.cursorPosition.x = short(n)
463			procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
464		case 'H':
465			token := strings.Split(buf.String(), ";")
466			if len(token) != 2 {
467				continue
468			}
469			n1, err := strconv.Atoi(token[0])
470			if err != nil {
471				continue
472			}
473			n2, err := strconv.Atoi(token[1])
474			if err != nil {
475				continue
476			}
477			csbi.cursorPosition.x = short(n2)
478			csbi.cursorPosition.x = short(n1)
479			procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
480		case 'J':
481			n, err := strconv.Atoi(buf.String())
482			if err != nil {
483				continue
484			}
485			var cursor coord
486			switch n {
487			case 0:
488				cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
489			case 1:
490				cursor = coord{x: csbi.window.left, y: csbi.window.top}
491			case 2:
492				cursor = coord{x: csbi.window.left, y: csbi.window.top}
493			}
494			var count, written dword
495			count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x)
496			procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
497			procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
498		case 'K':
499			n, err := strconv.Atoi(buf.String())
500			if err != nil {
501				continue
502			}
503			var cursor coord
504			switch n {
505			case 0:
506				cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
507			case 1:
508				cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y}
509			case 2:
510				cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y}
511			}
512			var count, written dword
513			count = dword(csbi.size.x - csbi.cursorPosition.x)
514			procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
515			procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
516		case 'm':
517			attr := csbi.attributes
518			cs := buf.String()
519			if cs == "" {
520				procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr))
521				continue
522			}
523			token := strings.Split(cs, ";")
524			for i := 0; i < len(token); i += 1 {
525				ns := token[i]
526				if n, err = strconv.Atoi(ns); err == nil {
527					switch {
528					case n == 0 || n == 100:
529						attr = w.oldattr
530					case 1 <= n && n <= 5:
531						attr |= foregroundIntensity
532					case n == 7:
533						attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
534					case 22 == n || n == 25 || n == 25:
535						attr |= foregroundIntensity
536					case n == 27:
537						attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
538					case 30 <= n && n <= 37:
539						attr = (attr & backgroundMask)
540						if (n-30)&1 != 0 {
541							attr |= foregroundRed
542						}
543						if (n-30)&2 != 0 {
544							attr |= foregroundGreen
545						}
546						if (n-30)&4 != 0 {
547							attr |= foregroundBlue
548						}
549					case n == 38: // set foreground color.
550						if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") {
551							if n256, err := strconv.Atoi(token[i+2]); err == nil {
552								if n256foreAttr == nil {
553									n256setup()
554								}
555								attr &= backgroundMask
556								attr |= n256foreAttr[n256]
557								i += 2
558							}
559						} else {
560							attr = attr & (w.oldattr & backgroundMask)
561						}
562					case n == 39: // reset foreground color.
563						attr &= backgroundMask
564						attr |= w.oldattr & foregroundMask
565					case 40 <= n && n <= 47:
566						attr = (attr & foregroundMask)
567						if (n-40)&1 != 0 {
568							attr |= backgroundRed
569						}
570						if (n-40)&2 != 0 {
571							attr |= backgroundGreen
572						}
573						if (n-40)&4 != 0 {
574							attr |= backgroundBlue
575						}
576					case n == 48: // set background color.
577						if i < len(token)-2 && token[i+1] == "5" {
578							if n256, err := strconv.Atoi(token[i+2]); err == nil {
579								if n256backAttr == nil {
580									n256setup()
581								}
582								attr &= foregroundMask
583								attr |= n256backAttr[n256]
584								i += 2
585							}
586						} else {
587							attr = attr & (w.oldattr & foregroundMask)
588						}
589					case n == 49: // reset foreground color.
590						attr &= foregroundMask
591						attr |= w.oldattr & backgroundMask
592					case 90 <= n && n <= 97:
593						attr = (attr & backgroundMask)
594						attr |= foregroundIntensity
595						if (n-90)&1 != 0 {
596							attr |= foregroundRed
597						}
598						if (n-90)&2 != 0 {
599							attr |= foregroundGreen
600						}
601						if (n-90)&4 != 0 {
602							attr |= foregroundBlue
603						}
604					case 100 <= n && n <= 107:
605						attr = (attr & foregroundMask)
606						attr |= backgroundIntensity
607						if (n-100)&1 != 0 {
608							attr |= backgroundRed
609						}
610						if (n-100)&2 != 0 {
611							attr |= backgroundGreen
612						}
613						if (n-100)&4 != 0 {
614							attr |= backgroundBlue
615						}
616					}
617					procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
618				}
619			}
620		}
621	}
622	return len(data) - w.lastbuf.Len(), nil
623}
624
625type consoleColor struct {
626	rgb       int
627	red       bool
628	green     bool
629	blue      bool
630	intensity bool
631}
632
633func (c consoleColor) foregroundAttr() (attr word) {
634	if c.red {
635		attr |= foregroundRed
636	}
637	if c.green {
638		attr |= foregroundGreen
639	}
640	if c.blue {
641		attr |= foregroundBlue
642	}
643	if c.intensity {
644		attr |= foregroundIntensity
645	}
646	return
647}
648
649func (c consoleColor) backgroundAttr() (attr word) {
650	if c.red {
651		attr |= backgroundRed
652	}
653	if c.green {
654		attr |= backgroundGreen
655	}
656	if c.blue {
657		attr |= backgroundBlue
658	}
659	if c.intensity {
660		attr |= backgroundIntensity
661	}
662	return
663}
664
665var color16 = []consoleColor{
666	consoleColor{0x000000, false, false, false, false},
667	consoleColor{0x000080, false, false, true, false},
668	consoleColor{0x008000, false, true, false, false},
669	consoleColor{0x008080, false, true, true, false},
670	consoleColor{0x800000, true, false, false, false},
671	consoleColor{0x800080, true, false, true, false},
672	consoleColor{0x808000, true, true, false, false},
673	consoleColor{0xc0c0c0, true, true, true, false},
674	consoleColor{0x808080, false, false, false, true},
675	consoleColor{0x0000ff, false, false, true, true},
676	consoleColor{0x00ff00, false, true, false, true},
677	consoleColor{0x00ffff, false, true, true, true},
678	consoleColor{0xff0000, true, false, false, true},
679	consoleColor{0xff00ff, true, false, true, true},
680	consoleColor{0xffff00, true, true, false, true},
681	consoleColor{0xffffff, true, true, true, true},
682}
683
684type hsv struct {
685	h, s, v float32
686}
687
688func (a hsv) dist(b hsv) float32 {
689	dh := a.h - b.h
690	switch {
691	case dh > 0.5:
692		dh = 1 - dh
693	case dh < -0.5:
694		dh = -1 - dh
695	}
696	ds := a.s - b.s
697	dv := a.v - b.v
698	return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv)))
699}
700
701func toHSV(rgb int) hsv {
702	r, g, b := float32((rgb&0xFF0000)>>16)/256.0,
703		float32((rgb&0x00FF00)>>8)/256.0,
704		float32(rgb&0x0000FF)/256.0
705	min, max := minmax3f(r, g, b)
706	h := max - min
707	if h > 0 {
708		if max == r {
709			h = (g - b) / h
710			if h < 0 {
711				h += 6
712			}
713		} else if max == g {
714			h = 2 + (b-r)/h
715		} else {
716			h = 4 + (r-g)/h
717		}
718	}
719	h /= 6.0
720	s := max - min
721	if max != 0 {
722		s /= max
723	}
724	v := max
725	return hsv{h: h, s: s, v: v}
726}
727
728type hsvTable []hsv
729
730func toHSVTable(rgbTable []consoleColor) hsvTable {
731	t := make(hsvTable, len(rgbTable))
732	for i, c := range rgbTable {
733		t[i] = toHSV(c.rgb)
734	}
735	return t
736}
737
738func (t hsvTable) find(rgb int) consoleColor {
739	hsv := toHSV(rgb)
740	n := 7
741	l := float32(5.0)
742	for i, p := range t {
743		d := hsv.dist(p)
744		if d < l {
745			l, n = d, i
746		}
747	}
748	return color16[n]
749}
750
751func minmax3f(a, b, c float32) (min, max float32) {
752	if a < b {
753		if b < c {
754			return a, c
755		} else if a < c {
756			return a, b
757		} else {
758			return c, b
759		}
760	} else {
761		if a < c {
762			return b, c
763		} else if b < c {
764			return b, a
765		} else {
766			return c, a
767		}
768	}
769}
770
771var n256foreAttr []word
772var n256backAttr []word
773
774func n256setup() {
775	n256foreAttr = make([]word, 256)
776	n256backAttr = make([]word, 256)
777	t := toHSVTable(color16)
778	for i, rgb := range color256 {
779		c := t.find(rgb)
780		n256foreAttr[i] = c.foregroundAttr()
781		n256backAttr[i] = c.backgroundAttr()
782	}
783}
784