1// Copyright 2019 Graham Clark. All rights reserved.  Use of this source code is governed by the MIT license
2// that can be found in the LICENSE file.
3
4package gowid
5
6import (
7	"fmt"
8	"regexp"
9	"strconv"
10
11	"github.com/gcla/gowid/gwutil"
12	"github.com/gdamore/tcell"
13	lru "github.com/hashicorp/golang-lru"
14	"github.com/lucasb-eyer/go-colorful"
15	"github.com/pkg/errors"
16)
17
18//======================================================================
19
20// These are used as bitmasks - a style is two AttrMasks. The first bitmask says whether or not the style declares an
21// e.g. underline setting; if it's declared, the second bitmask says whether or not underline is affirmatively on or off.
22// This allows styles to be layered e.g. the lower style declares underline is on, the upper style does not declare
23// an underline preference, so when layered, the cell is rendered with an underline.
24const (
25	StyleNoneSet tcell.AttrMask = 0 // Just unstyled text.
26	StyleAllSet  tcell.AttrMask = tcell.AttrBold | tcell.AttrBlink | tcell.AttrReverse | tcell.AttrUnderline | tcell.AttrDim
27)
28
29// StyleAttrs allows the user to represent a set of styles, either affirmatively set (on) or unset (off)
30// with the rest of the styles being unspecified, meaning they can be determined by styles layered
31// "underneath".
32type StyleAttrs struct {
33	OnOff tcell.AttrMask // If the specific bit in Set is 1, then the specific bit on OnOff says whether the style is on or off
34	Set   tcell.AttrMask // If the specific bit in Set is 0, then no style preference is declared (e.g. for underline)
35}
36
37// AllStyleMasks is an array of all the styles that can be applied to a Cell.
38var AllStyleMasks = [...]tcell.AttrMask{tcell.AttrBold, tcell.AttrBlink, tcell.AttrDim, tcell.AttrReverse, tcell.AttrUnderline}
39
40// StyleNone expresses no preference for any text styles.
41var StyleNone = StyleAttrs{}
42
43// StyleBold specifies the text should be bold, but expresses no preference for other text styles.
44var StyleBold = StyleAttrs{tcell.AttrBold, tcell.AttrBold}
45
46// StyleBlink specifies the text should blink, but expresses no preference for other text styles.
47var StyleBlink = StyleAttrs{tcell.AttrBlink, tcell.AttrBlink}
48
49// StyleDim specifies the text should be dim, but expresses no preference for other text styles.
50var StyleDim = StyleAttrs{tcell.AttrDim, tcell.AttrDim}
51
52// StyleReverse specifies the text should be displayed as reverse-video, but expresses no preference for other text styles.
53var StyleReverse = StyleAttrs{tcell.AttrReverse, tcell.AttrReverse}
54
55// StyleUnderline specifies the text should be underlined, but expresses no preference for other text styles.
56var StyleUnderline = StyleAttrs{tcell.AttrUnderline, tcell.AttrUnderline}
57
58// StyleBoldOnly specifies the text should be bold, and no other styling should apply.
59var StyleBoldOnly = StyleAttrs{tcell.AttrBold, StyleAllSet}
60
61// StyleBlinkOnly specifies the text should blink, and no other styling should apply.
62var StyleBlinkOnly = StyleAttrs{tcell.AttrBlink, StyleAllSet}
63
64// StyleDimOnly specifies the text should be dim, and no other styling should apply.
65var StyleDimOnly = StyleAttrs{tcell.AttrDim, StyleAllSet}
66
67// StyleReverseOnly specifies the text should be displayed reverse-video, and no other styling should apply.
68var StyleReverseOnly = StyleAttrs{tcell.AttrReverse, StyleAllSet}
69
70// StyleUnderlineOnly specifies the text should be underlined, and no other styling should apply.
71var StyleUnderlineOnly = StyleAttrs{tcell.AttrUnderline, StyleAllSet}
72
73// MergeUnder merges cell styles. E.g. if a is {underline, underline}, and upper is {!bold, bold}, that
74// means a declares that it should be rendered with underline and doesn't care about other styles; and
75// upper declares it should NOT be rendered bold, and doesn't declare about other styles. When merged,
76// the result is {underline|!bold, underline|bold}.
77func (a StyleAttrs) MergeUnder(upper StyleAttrs) StyleAttrs {
78	res := a
79	for _, am := range AllStyleMasks {
80		if (upper.Set & am) != 0 {
81			if (upper.OnOff & am) != 0 {
82				res.OnOff |= am
83			} else {
84				res.OnOff &= ^am
85			}
86			res.Set |= am
87		}
88	}
89	return res
90}
91
92//======================================================================
93
94// ColorMode represents the color capability of a terminal.
95type ColorMode int
96
97const (
98	// Mode256Colors represents a terminal with 256-color support.
99	Mode256Colors = ColorMode(iota)
100
101	// Mode88Colors represents a terminal with 88-color support such as rxvt.
102	Mode88Colors
103
104	// Mode16Colors represents a terminal with 16-color support.
105	Mode16Colors
106
107	// Mode8Colors represents a terminal with 8-color support.
108	Mode8Colors
109
110	// Mode8Colors represents a terminal with support for monochrome only.
111	ModeMonochrome
112
113	// Mode24BitColors represents a terminal with 24-bit color support like KDE's terminal.
114	Mode24BitColors
115)
116
117func (c ColorMode) String() string {
118	switch c {
119	case Mode256Colors:
120		return "256 colors"
121	case Mode88Colors:
122		return "88 colors"
123	case Mode16Colors:
124		return "16 colors"
125	case Mode8Colors:
126		return "8 colors"
127	case ModeMonochrome:
128		return "monochrome"
129	case Mode24BitColors:
130		return "24-bit truecolor"
131	default:
132		return fmt.Sprintf("Unknown (%d)", int(c))
133	}
134}
135
136const (
137	colorDefaultName      = "default"
138	colorBlackName        = "black"
139	colorRedName          = "red"
140	colorDarkRedName      = "dark red"
141	colorGreenName        = "green"
142	colorDarkGreenName    = "dark green"
143	colorBrownName        = "brown"
144	colorBlueName         = "blue"
145	colorDarkBlueName     = "dark blue"
146	colorMagentaName      = "magenta"
147	colorDarkMagentaName  = "dark magenta"
148	colorCyanName         = "cyan"
149	colorDarkCyanName     = "dark cyan"
150	colorLightGrayName    = "light gray"
151	colorDarkGrayName     = "dark gray"
152	colorLightRedName     = "light red"
153	colorLightGreenName   = "light green"
154	colorYellowName       = "yellow"
155	colorLightBlueName    = "light blue"
156	colorLightMagentaName = "light magenta"
157	colorLightCyanName    = "light cyan"
158	colorWhiteName        = "white"
159)
160
161var (
162	basicColors = map[string]int{
163		colorDefaultName:      0,
164		colorBlackName:        1,
165		colorDarkRedName:      2,
166		colorDarkGreenName:    3,
167		colorBrownName:        4,
168		colorDarkBlueName:     5,
169		colorDarkMagentaName:  6,
170		colorDarkCyanName:     7,
171		colorLightGrayName:    8,
172		colorDarkGrayName:     9,
173		colorLightRedName:     10,
174		colorLightGreenName:   11,
175		colorYellowName:       12,
176		colorLightBlueName:    13,
177		colorLightMagentaName: 14,
178		colorLightCyanName:    15,
179		colorWhiteName:        16,
180		colorRedName:          10,
181		colorGreenName:        11,
182		colorBlueName:         13,
183		colorMagentaName:      14,
184		colorCyanName:         15,
185	}
186
187	tBasicColors = map[string]int{
188		colorDefaultName:      0,
189		colorBlackName:        1,
190		colorDarkRedName:      2,
191		colorDarkGreenName:    3,
192		colorBrownName:        4,
193		colorDarkBlueName:     5,
194		colorDarkMagentaName:  6,
195		colorDarkCyanName:     7,
196		colorLightGrayName:    8,
197		colorDarkGrayName:     1,
198		colorLightRedName:     2,
199		colorLightGreenName:   3,
200		colorYellowName:       4,
201		colorLightBlueName:    5,
202		colorLightMagentaName: 6,
203		colorLightCyanName:    7,
204		colorWhiteName:        8,
205		colorRedName:          2,
206		colorGreenName:        3,
207		colorBlueName:         5,
208		colorMagentaName:      6,
209		colorCyanName:         7,
210	}
211
212	CubeStart    = 16 // first index of color cube
213	CubeSize256  = 6  // one side of the color cube
214	graySize256  = 24
215	grayStart256 = gwutil.IPow(CubeSize256, 3) + CubeStart
216	cubeWhite256 = grayStart256 - 1
217	cubeSize88   = 4
218	graySize88   = 8
219	grayStart88  = gwutil.IPow(cubeSize88, 3) + CubeStart
220	cubeWhite88  = grayStart88 - 1
221	cubeBlack    = CubeStart
222
223	cubeSteps256 = []int{0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff}
224	graySteps256 = []int{
225		0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62,
226		0x6c, 0x76, 0x80, 0x84, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0,
227		0xda, 0xe4, 0xee,
228	}
229
230	cubeSteps88 = []int{0x00, 0x8b, 0xcd, 0xff}
231	graySteps88 = []int{0x2e, 0x5c, 0x73, 0x8b, 0xa2, 0xb9, 0xd0, 0xe7}
232
233	cubeLookup256 = makeColorLookup(cubeSteps256, 256)
234	grayLookup256 = makeColorLookup(append([]int{0x00}, append(graySteps256, 0xff)...), 256)
235
236	cubeLookup88 = makeColorLookup(cubeSteps88, 256)
237	grayLookup88 = makeColorLookup(append([]int{0x00}, append(graySteps88, 0xff)...), 256)
238
239	cubeLookup256_16  []int
240	grayLookup256_101 []int
241
242	cubeLookup88_16  []int
243	grayLookup88_101 []int
244
245	// ColorNone means no preference if anything is layered underneath
246	ColorNone = MakeTCellNoColor()
247
248	// ColorDefault is an affirmative preference for the default terminal color
249	ColorDefault = MakeTCellColorExt(tcell.ColorDefault)
250
251	// Some pre-initialized color objects for use in applications e.g.
252	// MakePaletteEntry(ColorBlack, ColorRed)
253	ColorBlack      = MakeTCellColorExt(tcell.ColorBlack)
254	ColorRed        = MakeTCellColorExt(tcell.ColorRed)
255	ColorGreen      = MakeTCellColorExt(tcell.ColorGreen)
256	ColorLightGreen = MakeTCellColorExt(tcell.ColorLightGreen)
257	ColorYellow     = MakeTCellColorExt(tcell.ColorYellow)
258	ColorBlue       = MakeTCellColorExt(tcell.ColorBlue)
259	ColorLightBlue  = MakeTCellColorExt(tcell.ColorLightBlue)
260	ColorMagenta    = MakeTCellColorExt(tcell.ColorDarkMagenta)
261	ColorCyan       = MakeTCellColorExt(tcell.ColorDarkCyan)
262	ColorWhite      = MakeTCellColorExt(tcell.ColorWhite)
263	ColorDarkRed    = MakeTCellColorExt(tcell.ColorDarkRed)
264	ColorDarkGreen  = MakeTCellColorExt(tcell.ColorDarkGreen)
265	ColorDarkBlue   = MakeTCellColorExt(tcell.ColorDarkBlue)
266	ColorLightGray  = MakeTCellColorExt(tcell.ColorLightGray)
267	ColorDarkGray   = MakeTCellColorExt(tcell.ColorDarkGray)
268	ColorPurple     = MakeTCellColorExt(tcell.ColorPurple)
269	ColorOrange     = MakeTCellColorExt(tcell.ColorOrange)
270
271	longColorRE    = regexp.MustCompile(`^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$`)
272	shortColorRE   = regexp.MustCompile(`^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$`)
273	grayHexColorRE = regexp.MustCompile(`^g#([0-9a-fA-F][0-9a-fA-F])$`)
274	grayDecColorRE = regexp.MustCompile(`^g(1?[0-9][0-9]?)$`)
275
276	colorfulBlack8   = colorful.Color{R: 0.0, G: 0.0, B: 0.0}
277	colorfulWhite8   = colorful.Color{R: 1.0, G: 1.0, B: 1.0}
278	colorfulRed8     = colorful.Color{R: 1.0, G: 0.0, B: 0.0}
279	colorfulGreen8   = colorful.Color{R: 0.0, G: 1.0, B: 0.0}
280	colorfulBlue8    = colorful.Color{R: 0.0, G: 0.0, B: 1.0}
281	colorfulYellow8  = colorful.Color{R: 1.0, G: 1.0, B: 0.0}
282	colorfulMagenta8 = colorful.Color{R: 1.0, G: 0.0, B: 1.0}
283	colorfulCyan8    = colorful.Color{R: 0.0, G: 1.0, B: 1.0}
284
285	colorfulBlack16         = colorful.Color{R: 0.0, G: 0.0, B: 0.0}
286	colorfulWhite16         = colorful.Color{R: 0.66, G: 0.66, B: 0.66}
287	colorfulRed16           = colorful.Color{R: 0.5, G: 0.0, B: 0.0}
288	colorfulGreen16         = colorful.Color{R: 0.0, G: 0.5, B: 0.0}
289	colorfulBlue16          = colorful.Color{R: 0.0, G: 0.0, B: 0.5}
290	colorfulYellow16        = colorful.Color{R: 0.5, G: 0.5, B: 0.5}
291	colorfulMagenta16       = colorful.Color{R: 0.5, G: 0.0, B: 0.5}
292	colorfulCyan16          = colorful.Color{R: 0.0, G: 0.5, B: 0.5}
293	colorfulBrightBlack16   = colorful.Color{R: 0.33, G: 0.33, B: 0.33}
294	colorfulBrightWhite16   = colorful.Color{R: 1.0, G: 1.0, B: 1.0}
295	colorfulBrightRed16     = colorful.Color{R: 1.0, G: 0.0, B: 0.0}
296	colorfulBrightGreen16   = colorful.Color{R: 0.0, G: 1.0, B: 0.0}
297	colorfulBrightBlue16    = colorful.Color{R: 0.0, G: 0.0, B: 1.0}
298	colorfulBrightYellow16  = colorful.Color{R: 1.0, G: 1.0, B: 1.0}
299	colorfulBrightMagenta16 = colorful.Color{R: 1.0, G: 0.0, B: 1.0}
300	colorfulBrightCyan16    = colorful.Color{R: 0.0, G: 1.0, B: 1.0}
301
302	// Used in mapping RGB colors down to 8 terminal colors.
303	colorful8 = []colorful.Color{
304		colorfulBlack8,
305		colorfulWhite8,
306		colorfulRed8,
307		colorfulGreen8,
308		colorfulBlue8,
309		colorfulYellow8,
310		colorfulMagenta8,
311		colorfulCyan8,
312	}
313
314	// Used in mapping RGB colors down to 16 terminal colors.
315	colorful16 = []colorful.Color{
316		colorfulBlack16,
317		colorfulWhite16,
318		colorfulRed16,
319		colorfulGreen16,
320		colorfulBlue16,
321		colorfulYellow16,
322		colorfulMagenta16,
323		colorfulCyan16,
324		colorfulBrightBlack16,
325		colorfulBrightWhite16,
326		colorfulBrightRed16,
327		colorfulBrightGreen16,
328		colorfulBrightBlue16,
329		colorfulBrightYellow16,
330		colorfulBrightMagenta16,
331		colorfulBrightCyan16,
332	}
333
334	term8 = []TCellColor{
335		ColorBlack,
336		ColorWhite,
337		ColorRed,
338		ColorGreen,
339		ColorBlue,
340		ColorYellow,
341		ColorMagenta,
342		ColorCyan,
343	}
344
345	term16 = []TCellColor{
346		ColorBlack,
347		ColorLightGray,
348		ColorDarkRed,
349		ColorDarkGreen,
350		ColorDarkBlue,
351		ColorYellow,
352		ColorMagenta,
353		ColorCyan,
354		ColorDarkGray,
355		ColorWhite,
356		ColorRed,
357		ColorGreen,
358		ColorBlue,
359		ColorYellow,
360		ColorMagenta,
361		ColorCyan, // TODO - figure out these colors
362	}
363
364	term2Cache  *lru.Cache
365	term8Cache  *lru.Cache
366	term16Cache *lru.Cache
367)
368
369//======================================================================
370
371func init() {
372	cubeLookup256_16 = make([]int, 16)
373	cubeLookup88_16 = make([]int, 16)
374	grayLookup256_101 = make([]int, 101)
375	grayLookup88_101 = make([]int, 101)
376
377	for i := 0; i < 16; i++ {
378		cubeLookup256_16[i] = cubeLookup256[intScale(i, 16, 0x100)]
379		cubeLookup88_16[i] = cubeLookup88[intScale(i, 16, 0x100)]
380	}
381	for i := 0; i < 101; i++ {
382		grayLookup256_101[i] = grayLookup256[intScale(i, 101, 0x100)]
383		grayLookup88_101[i] = grayLookup88[intScale(i, 101, 0x100)]
384	}
385
386	var err error
387	for _, cache := range []**lru.Cache{&term2Cache, &term8Cache, &term16Cache} {
388		*cache, err = lru.New(100)
389		if err != nil {
390			panic(err)
391		}
392	}
393}
394
395// makeColorLookup([0, 7, 9], 10)
396// [0, 0, 0, 0, 1, 1, 1, 1, 2, 2]
397//
398func makeColorLookup(vals []int, length int) []int {
399	res := make([]int, length)
400
401	vi := 0
402	for i := 0; i < len(res); i++ {
403		if vi+1 < len(vals) {
404			if i <= (vals[vi]+vals[vi+1])/2 {
405				res[i] = vi
406			} else {
407				vi++
408				res[i] = vi
409			}
410		} else if vi < len(vals) {
411			// only last vi is valid
412			res[i] = vi
413		}
414	}
415	return res
416}
417
418// Scale val in the range [0, val_range-1] to an integer in the range
419// [0, out_range-1].  This implementation uses the "round-half-up" rounding
420// method.
421//
422func intScale(val int, val_range int, out_range int) int {
423	num := val*(out_range-1)*2 + (val_range - 1)
424	dem := (val_range - 1) * 2
425	return num / dem
426}
427
428//======================================================================
429
430type ColorModeMismatch struct {
431	Color IColor
432	Mode  ColorMode
433}
434
435var _ error = ColorModeMismatch{}
436
437func (e ColorModeMismatch) Error() string {
438	return fmt.Sprintf("Color %v of type %T not supported in mode %v", e.Color, e.Color, e.Mode)
439}
440
441type InvalidColor struct {
442	Color interface{}
443}
444
445var _ error = InvalidColor{}
446
447func (e InvalidColor) Error() string {
448	return fmt.Sprintf("Color %v of type %T is invalid", e.Color, e.Color)
449}
450
451//======================================================================
452
453// ICellStyler is an analog to urwid's AttrSpec (http://urwid.org/reference/attrspec.html). When provided
454// a RenderContext (specifically the color mode in which to be rendered), the GetStyle() function will
455// return foreground, background and style values with which a cell should be rendered. The IRenderContext
456// argument provides access to the global palette, so an ICellStyle implementation can look up palette
457// entries by name.
458type ICellStyler interface {
459	GetStyle(IRenderContext) (IColor, IColor, StyleAttrs)
460}
461
462// IColor is implemented by any object that can turn itself into a TCellColor, meaning a color with
463// which a cell can be rendered. The display mode (e.g. 256 colors) is provided. If no TCellColor is
464// available, the second argument should be set to false e.g. no color can be found given a particular
465// string name.
466type IColor interface {
467	ToTCellColor(mode ColorMode) (TCellColor, bool)
468}
469
470// MakeCellStyle constructs a tcell.Style from gowid colors and styles. The return value can be provided
471// to tcell in order to style a particular region of the screen.
472func MakeCellStyle(fg TCellColor, bg TCellColor, attr StyleAttrs) tcell.Style {
473	var fgt, bgt tcell.Color
474	if fg == ColorNone {
475		fgt = tcell.ColorDefault
476	} else {
477		fgt = fg.ToTCell()
478	}
479	if bg == ColorNone {
480		bgt = tcell.ColorDefault
481	} else {
482		bgt = bg.ToTCell()
483	}
484	st := StyleNone.MergeUnder(attr)
485	return tcell.Style(st.OnOff).Foreground(fgt).Background(bgt)
486}
487
488//======================================================================
489
490// Color satisfies IColor, embeds an IColor, and allows a gowid Color to be
491// constructed from a string alone. Each of the more specific color types is
492// tried in turn with the string until one succeeds.
493type Color struct {
494	IColor
495	Id string
496}
497
498func (c Color) String() string {
499	return fmt.Sprintf("%v", c.IColor)
500}
501
502// MakeColorSafe returns a Color struct specified by the string argument, in a
503// do-what-I-mean fashion - it tries the Color struct maker functions in
504// a pre-determined order until one successfully initialized a Color, or
505// until all fail - in which case an error is returned. The order tried is
506// TCellColor, RGBColor, GrayColor, UrwidColor.
507func MakeColorSafe(s string) (Color, error) {
508	var col IColor
509	var err error
510	col, err = MakeTCellColor(s)
511	if err == nil {
512		return Color{col, s}, nil
513	}
514	col, err = MakeRGBColorSafe(s)
515	if err == nil {
516		return Color{col, s}, nil
517	}
518	col, err = MakeGrayColorSafe(s)
519	if err == nil {
520		return Color{col, s}, nil
521	}
522	col, err = NewUrwidColorSafe(s)
523	if err == nil {
524		return Color{col, s}, nil
525	}
526
527	return Color{}, errors.WithStack(InvalidColor{Color: s})
528}
529
530func MakeColor(s string) Color {
531	res, err := MakeColorSafe(s)
532	if err != nil {
533		panic(err)
534	}
535	return res
536}
537
538//======================================================================
539
540type ColorByMode struct {
541	Colors map[ColorMode]IColor // Indexed by ColorMode
542}
543
544var _ IColor = (*ColorByMode)(nil)
545
546func MakeColorByMode(cols map[ColorMode]IColor) ColorByMode {
547	res, err := MakeColorByModeSafe(cols)
548	if err != nil {
549		panic(err)
550	}
551	return res
552}
553
554func MakeColorByModeSafe(cols map[ColorMode]IColor) (ColorByMode, error) {
555	return ColorByMode{Colors: cols}, nil
556}
557
558func (c ColorByMode) ToTCellColor(mode ColorMode) (TCellColor, bool) {
559	if col, ok := c.Colors[mode]; ok {
560		col2, ok := col.ToTCellColor(mode)
561		return col2, ok
562	}
563	panic(ColorModeMismatch{Color: c, Mode: mode})
564}
565
566//======================================================================
567
568// RGBColor allows for use of colors specified as three components, each with values from 0x0 to 0xf.
569// Note that an RGBColor should render as close to the components specify regardless of the color mode
570// of the terminal - 24-bit color, 256-color, 88-color. Gowid constructs a color cube, just like urwid,
571// and for each color mode, has a lookup table that maps the rgb values to a color cube value which is
572// closest to the intended color. Note that RGBColor is not supported in 16-color, 8-color or
573// monochrome.
574type RGBColor struct {
575	Red, Green, Blue int
576}
577
578var _ IColor = (*RGBColor)(nil)
579
580// MakeRGBColor constructs an RGBColor from a string e.g. "#f00" is red. Note that
581// MakeRGBColorSafe should be used unless you are sure the string provided is valid
582// (otherwise there will be a panic).
583func MakeRGBColor(s string) RGBColor {
584	res, err := MakeRGBColorSafe(s)
585	if err != nil {
586		panic(err)
587	}
588	return res
589}
590
591func (r RGBColor) String() string {
592	return fmt.Sprintf("RGBColor(#%02x,#%02x,#%02x)", r.Red, r.Green, r.Blue)
593}
594
595// MakeRGBColorSafe does the same as MakeRGBColor except will return an
596// error if provided with invalid input.
597func MakeRGBColorSafe(s string) (RGBColor, error) {
598	var mult int64 = 1
599	match := longColorRE.FindAllStringSubmatch(s, -1)
600	if len(match) == 0 {
601		match = shortColorRE.FindAllStringSubmatch(s, -1)
602		if len(match) == 0 {
603			return RGBColor{}, errors.WithStack(InvalidColor{Color: s})
604		}
605		mult = 16
606	}
607
608	d1, _ := strconv.ParseInt(match[0][1], 16, 16)
609	d2, _ := strconv.ParseInt(match[0][2], 16, 16)
610	d3, _ := strconv.ParseInt(match[0][3], 16, 16)
611
612	d1 *= mult
613	d2 *= mult
614	d3 *= mult
615
616	x := MakeRGBColorExt(int(d1), int(d2), int(d3))
617	return x, nil
618}
619
620// MakeRGBColorExtSafe builds an RGBColor from the red, green and blue components
621// provided as integers. If the values are out of range, an error is returned.
622func MakeRGBColorExtSafe(r, g, b int) (RGBColor, error) {
623	col := RGBColor{r, g, b}
624	if r > 0xff || g > 0xff || b > 0xff {
625		return RGBColor{}, errors.WithStack(errors.WithMessage(InvalidColor{Color: col}, "RGBColor parameters must be between 0x00 and 0xfff"))
626	}
627	return col, nil
628}
629
630// MakeRGBColorExt builds an RGBColor from the red, green and blue components
631// provided as integers. If the values are out of range, the function will panic.
632func MakeRGBColorExt(r, g, b int) RGBColor {
633	res, err := MakeRGBColorExtSafe(r, g, b)
634	if err != nil {
635		panic(err)
636	}
637
638	return res
639}
640
641// Implements golang standard library's color.Color
642func (rgb RGBColor) RGBA() (r, g, b, a uint32) {
643	r = uint32(rgb.Red << 8)
644	g = uint32(rgb.Green << 8)
645	b = uint32(rgb.Blue << 8)
646	a = 0xffff
647	return
648}
649
650func (r RGBColor) findClosest(from []colorful.Color, corresponding []TCellColor, cache *lru.Cache) TCellColor {
651	var best float64 = 100.0
652	var j int
653
654	if res, ok := cache.Get(r); ok {
655		return res.(TCellColor)
656	}
657
658	ccol, _ := colorful.MakeColor(r)
659
660	for i, c := range from {
661		x := c.DistanceLab(ccol)
662		if x < best {
663			best = x
664			j = i
665		}
666	}
667
668	cache.Add(r, corresponding[j])
669
670	return corresponding[j]
671}
672
673// ToTCellColor converts an RGBColor to a TCellColor, suitable for rendering to the screen
674// with tcell. It lets RGBColor conform to IColor.
675func (r RGBColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
676	switch mode {
677	case Mode24BitColors:
678		c := tcell.Color((r.Red << 16) | (r.Green << 8) | (r.Blue << 0) | int(tcell.ColorIsRGB))
679		return MakeTCellColorExt(c), true
680	case Mode256Colors:
681		rd := cubeLookup256_16[r.Red>>4]
682		g := cubeLookup256_16[r.Green>>4]
683		b := cubeLookup256_16[r.Blue>>4]
684		c := tcell.Color((CubeStart + (((rd * CubeSize256) + g) * CubeSize256) + b) + 0)
685		return MakeTCellColorExt(c), true
686	case Mode88Colors:
687		rd := cubeLookup88_16[r.Red>>4]
688		g := cubeLookup88_16[r.Green>>4]
689		b := cubeLookup88_16[r.Blue>>4]
690		c := tcell.Color((CubeStart + (((rd * cubeSize88) + g) * cubeSize88) + b) + 0)
691		return MakeTCellColorExt(c), true
692	case Mode16Colors:
693		return r.findClosest(colorful16, term16, term16Cache), true
694	case Mode8Colors:
695		return r.findClosest(colorful8, term8, term8Cache), true
696	case ModeMonochrome:
697		return r.findClosest(colorful8[0:1], term8[0:1], term2Cache), true
698	default:
699		return TCellColor{}, false
700	}
701}
702
703//======================================================================
704
705// UrwidColor is a gowid Color implementing IColor and which allows urwid color names to be used
706// (http://urwid.org/manual/displayattributes.html#foreground-and-background-settings) e.g.
707// "dark blue", "light gray".
708type UrwidColor struct {
709	Id     string
710	cached bool
711	cache  [2]TCellColor
712}
713
714var _ IColor = (*UrwidColor)(nil)
715
716// NewUrwidColorSafe returns a pointer to an UrwidColor struct and builds the UrwidColor from
717// a string argument e.g. "yellow". Note that in urwid proper (python), a color can also specify
718// a style, like "yellow, underline". UrwidColor does not support specifying styles in that manner.
719func NewUrwidColorSafe(val string) (*UrwidColor, error) {
720	return &UrwidColor{
721		Id: val,
722	}, nil
723}
724
725// NewUrwidColorSafe returns a pointer to an UrwidColor struct and builds the UrwidColor from
726// a string argument e.g. "yellow"; this function will panic if the there is an error during
727// initialization.
728func NewUrwidColor(val string) *UrwidColor {
729	res, err := NewUrwidColorSafe(val)
730	if err != nil {
731		panic(err)
732	}
733
734	return res
735}
736
737func (r UrwidColor) String() string {
738	return fmt.Sprintf("UrwidColor(%s)", r.Id)
739}
740
741// ToTCellColor converts the receiver UrwidColor to a TCellColor, ready for rendering to a
742// tcell screen. This lets UrwidColor conform to IColor.
743func (s *UrwidColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
744	if s.cached {
745		switch mode {
746		case Mode24BitColors, Mode256Colors, Mode88Colors, Mode16Colors:
747			return s.cache[0], true
748		case Mode8Colors, ModeMonochrome:
749			return s.cache[1], true
750		default:
751			panic(errors.WithStack(ColorModeMismatch{Color: s, Mode: mode}))
752		}
753	}
754
755	idx := -1
756	switch mode {
757	case Mode24BitColors, Mode256Colors, Mode88Colors, Mode16Colors:
758		idx = posInMap(s.Id, basicColors)
759	case Mode8Colors, ModeMonochrome:
760		idx = posInMap(s.Id, tBasicColors)
761	default:
762		panic(errors.WithStack(ColorModeMismatch{Color: s, Mode: mode}))
763	}
764
765	if idx == -1 {
766		panic(errors.WithStack(InvalidColor{Color: s}))
767	}
768
769	idx = idx - 1 // offset for tcell, which stores default at -1
770
771	c := MakeTCellColorExt(tcell.Color(idx))
772
773	switch mode {
774	case Mode24BitColors, Mode256Colors, Mode88Colors, Mode16Colors:
775		s.cache[0] = c
776	case Mode8Colors, ModeMonochrome:
777		s.cache[1] = c
778	}
779	s.cached = true
780
781	return c, true
782}
783
784//======================================================================
785
786// GrayColor is an IColor that represents a greyscale specified by the
787// same syntax as urwid - http://urwid.org/manual/displayattributes.html
788// and search for "gray scale entries". Strings may be of the form "g3",
789// "g100" or "g#a1", "g#ff" if hexadecimal is preferred. These index the
790// grayscale color cube.
791type GrayColor struct {
792	Val int
793}
794
795func (g GrayColor) String() string {
796	return fmt.Sprintf("GrayColor(%d)", g.Val)
797}
798
799// MakeGrayColorSafe returns an initialized GrayColor provided with a string
800// input like "g50" or "g#ab". If the input is invalid, an error is returned.
801func MakeGrayColorSafe(val string) (GrayColor, error) {
802	var d uint64
803	match := grayDecColorRE.FindAllStringSubmatch(val, -1)
804	if len(match) == 0 || len(match[0]) != 2 {
805		match := grayHexColorRE.FindAllStringSubmatch(val, -1)
806		if len(match) == 0 || len(match[0]) != 2 {
807			return GrayColor{}, errors.WithStack(InvalidColor{Color: val})
808		}
809		d, _ = strconv.ParseUint(match[0][1], 16, 8)
810	} else {
811		d, _ = strconv.ParseUint(match[0][1], 10, 8)
812		if d > 100 {
813			return GrayColor{}, errors.WithStack(InvalidColor{Color: val})
814		}
815	}
816
817	return GrayColor{int(d)}, nil
818}
819
820// MakeGrayColor returns an initialized GrayColor provided with a string
821// input like "g50" or "g#ab". If the input is invalid, the function panics.
822func MakeGrayColor(val string) GrayColor {
823	res, err := MakeGrayColorSafe(val)
824	if err != nil {
825		panic(err)
826	}
827
828	return res
829}
830
831func grayAdjustment88(val int) int {
832	if val == 0 {
833		return cubeBlack
834	}
835	val -= 1
836	if val == graySize88 {
837		return cubeWhite88
838	}
839	y := grayStart88 + val
840	return y
841}
842
843func grayAdjustment256(val int) int {
844	if val == 0 {
845		return cubeBlack
846	}
847	val -= 1
848	if val == graySize256 {
849		return cubeWhite256
850	}
851	y := grayStart256 + val
852	return y
853}
854
855// ToTCellColor converts the receiver GrayColor to a TCellColor, ready for rendering to a
856// tcell screen. This lets GrayColor conform to IColor.
857func (s GrayColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
858	switch mode {
859	case Mode24BitColors:
860		adj := intScale(s.Val, 101, 0x100)
861		c := tcell.Color((adj << 16) | (adj << 8) | (adj << 0) | int(tcell.ColorIsRGB))
862		return MakeTCellColorExt(c), true
863	case Mode256Colors:
864		x := tcell.Color(grayAdjustment256(grayLookup256_101[s.Val]) + 1)
865		return MakeTCellColorExt(x), true
866	case Mode88Colors:
867		x := tcell.Color(grayAdjustment88(grayLookup88_101[s.Val]) + 1)
868		return MakeTCellColorExt(x), true
869	default:
870		panic(errors.WithStack(ColorModeMismatch{Color: s, Mode: mode}))
871	}
872}
873
874//======================================================================
875
876// TCellColor is an IColor using tcell's color primitives. If you are not porting from urwid or translating
877// from urwid, this is the simplest approach to using color. Note that the underlying tcell.Color value is
878// stored offset by 2 from the value tcell would use to actually render a colored cell. Tcell represents
879// e.g. black by 0, maroon by 1, and so on - that means the default/empty/zero value for a tcell.Color object
880// is the color black. Gowid's layering approach means that the empty value for a color should mean "no color
881// preference" - so we want the zero value to mean that. A tcell.Color of -1 means "default color". So gowid
882// coopts -2 to mean "no color preference". We store the tcell.Color offset by 2 so the empty value for a
883// TCellColor means "no color preference". When we convert to a tcell.Color, we subtract 2 (but since a value
884// of -2 is meaningless to tcell, the caller should check and not pass on a value of -2 to tcell APIs - see
885// gowid.ColorNone)
886type TCellColor struct {
887	tc tcell.Color
888}
889
890var (
891	_ IColor       = (*TCellColor)(nil)
892	_ fmt.Stringer = (*TCellColor)(nil)
893)
894
895// MakeTCellColor returns an initialized TCellColor given a string input like "yellow". The names that can be
896// used are provided here: https://github.com/gdamore/tcell/blob/master/color.go#L821.
897func MakeTCellColor(val string) (TCellColor, error) {
898	if col, ok := tcell.ColorNames[val]; !ok {
899		return TCellColor{}, errors.WithStack(InvalidColor{Color: val})
900	} else {
901		return MakeTCellColorExt(col), nil
902	}
903}
904
905// MakeTCellColor returns an initialized TCellColor given a tcell.Color input. The values that can be
906// used are provided here: https://github.com/gdamore/tcell/blob/master/color.go#L41.
907func MakeTCellColorSafe(val tcell.Color) (TCellColor, error) {
908	return TCellColor{val + 2}, nil
909}
910
911// MakeTCellColor returns an initialized TCellColor given a tcell.Color input. The values that can be
912// used are provided here: https://github.com/gdamore/tcell/blob/master/color.go#L41.
913func MakeTCellColorExt(val tcell.Color) TCellColor {
914	res, _ := MakeTCellColorSafe(val)
915	return res
916}
917
918// MakeTCellNoColor returns an initialized TCellColor that represents "no color" - meaning if another
919// color is rendered "under" this one, then the color underneath will be displayed.
920func MakeTCellNoColor() TCellColor {
921	res := MakeTCellColorExt(-2)
922	return res
923}
924
925// String implements Stringer for '%v' support.
926func (r TCellColor) String() string {
927	c := r.tc - 2
928	if c == -2 {
929		return "[no-color]"
930	} else {
931		return fmt.Sprintf("TCellColor(%v)", tcell.Color(c))
932	}
933}
934
935// ToTCell converts a TCellColor back to a tcell.Color for passing to tcell APIs.
936func (r TCellColor) ToTCell() tcell.Color {
937	return r.tc - 2
938}
939
940// ToTCellColor is a no-op, and exists so that TCellColor conforms to the IColor interface.
941func (r TCellColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
942	return r, true
943}
944
945//======================================================================
946
947// NoColor implements IColor, and represents "no color preference", distinct from the default terminal color,
948// white, black, etc. This means that if a NoColor is rendered over another color, the color underneath will
949// be displayed.
950type NoColor struct{}
951
952// ToTCellColor converts NoColor to TCellColor. This lets NoColor conform to the IColor interface.
953func (r NoColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
954	return ColorNone, true
955}
956
957func (r NoColor) String() string {
958	return "NoColor"
959}
960
961//======================================================================
962
963// DefaultColor implements IColor and means use whatever the default terminal color is. This is
964// different to NoColor, which expresses no preference.
965type DefaultColor struct{}
966
967// ToTCellColor converts DefaultColor to TCellColor. This lets DefaultColor conform to the IColor interface.
968func (r DefaultColor) ToTCellColor(mode ColorMode) (TCellColor, bool) {
969	return MakeTCellColorExt(tcell.ColorDefault), true
970}
971
972func (r DefaultColor) String() string {
973	return "DefaultColor"
974}
975
976//======================================================================
977
978// ColorInverter implements ICellStyler, and simply swaps foreground and background colors.
979type ColorInverter struct {
980	ICellStyler
981}
982
983func (c ColorInverter) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
984	y, x, z = c.ICellStyler.GetStyle(prov)
985	return
986}
987
988//======================================================================
989
990// PaletteEntry is typically used by a gowid application to represent a set of color and style
991// preferences for use by different application widgets e.g. black text on a white background
992// with text underlined. PaletteEntry implements the ICellStyler interface meaning it can
993// provide a triple of foreground and background IColor, and a StyleAttrs struct.
994type PaletteEntry struct {
995	FG    IColor
996	BG    IColor
997	Style StyleAttrs
998}
999
1000var _ ICellStyler = (*PaletteEntry)(nil)
1001
1002// MakeStyledPaletteEntry simply stores the three parameters provided - a foreground and
1003// background IColor, and a StyleAttrs struct.
1004func MakeStyledPaletteEntry(fg, bg IColor, style StyleAttrs) PaletteEntry {
1005	return PaletteEntry{fg, bg, style}
1006}
1007
1008// MakePaletteEntry stores the two IColor parameters provided, and has no style preference.
1009func MakePaletteEntry(fg, bg IColor) PaletteEntry {
1010	return PaletteEntry{fg, bg, StyleNone}
1011}
1012
1013// GetStyle returns the individual colors and style attributes.
1014func (a PaletteEntry) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
1015	x, y, z = a.FG, a.BG, a.Style
1016	return
1017}
1018
1019//======================================================================
1020
1021// PaletteRef is intended to represent a PaletteEntry, looked up by name. The ICellStyler
1022// API GetStyle() provides an IRenderContext and should return two colors and style attributes.
1023// PaletteRef provides these by looking up the IRenderContext with the name (string) provided
1024// to it at initialization.
1025type PaletteRef struct {
1026	Name string
1027}
1028
1029var _ ICellStyler = (*PaletteRef)(nil)
1030
1031// MakePaletteRef returns a PaletteRef struct storing the (string) name of the PaletteEntry
1032// which will be looked up in the IRenderContext.
1033func MakePaletteRef(name string) PaletteRef {
1034	return PaletteRef{name}
1035}
1036
1037// GetStyle returns the two colors and a style, looked up in the IRenderContext by name.
1038func (a PaletteRef) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
1039	spec, ok := prov.CellStyler(a.Name)
1040	if ok {
1041		x, y, z = spec.GetStyle(prov)
1042	} else {
1043		x, y, z = NoColor{}, NoColor{}, StyleAttrs{}
1044	}
1045	return
1046}
1047
1048//======================================================================
1049
1050// EmptyPalette implements ICellStyler and returns no preference for any colors or styling.
1051type EmptyPalette struct{}
1052
1053var _ ICellStyler = (*EmptyPalette)(nil)
1054
1055func MakeEmptyPalette() EmptyPalette {
1056	return EmptyPalette{}
1057}
1058
1059// GetStyle implements ICellStyler.
1060func (a EmptyPalette) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
1061	x, y, z = NoColor{}, NoColor{}, StyleAttrs{}
1062	return
1063}
1064
1065//======================================================================
1066
1067// StyleMod implements ICellStyler. It returns colors and styles from its Cur field unless they are
1068// overridden by settings in its Mod field. This provides a way for a layering of ICellStylers.
1069type StyleMod struct {
1070	Cur ICellStyler
1071	Mod ICellStyler
1072}
1073
1074var _ ICellStyler = (*StyleMod)(nil)
1075
1076// MakeStyleMod implements ICellStyler and stores two ICellStylers, one to layer on top of the
1077// other.
1078func MakeStyleMod(cur, mod ICellStyler) StyleMod {
1079	return StyleMod{cur, mod}
1080}
1081
1082// GetStyle returns the IColors and StyleAttrs from the Mod ICellStyler if they express an
1083// affirmative preference, otherwise defers to the values from the Cur ICellStyler.
1084func (a StyleMod) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
1085	fcur, bcur, scur := a.Cur.GetStyle(prov)
1086	fmod, bmod, smod := a.Mod.GetStyle(prov)
1087	var ok bool
1088	_, ok = fmod.ToTCellColor(prov.GetColorMode())
1089	if ok {
1090		x = fmod
1091	} else {
1092		x = fcur
1093	}
1094	_, ok = bmod.ToTCellColor(prov.GetColorMode())
1095	if ok {
1096		y = bmod
1097	} else {
1098		y = bcur
1099	}
1100	z = scur.MergeUnder(smod)
1101	return
1102}
1103
1104//======================================================================
1105
1106// ForegroundColor is an ICellStyler that expresses a specific foreground color and no preference for
1107// background color or style.
1108type ForegroundColor struct {
1109	IColor
1110}
1111
1112var _ ICellStyler = (*ForegroundColor)(nil)
1113
1114func MakeForeground(c IColor) ForegroundColor {
1115	return ForegroundColor{c}
1116}
1117
1118// GetStyle implements ICellStyler.
1119func (a ForegroundColor) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
1120	x = a.IColor
1121	y = NoColor{}
1122	z = StyleNone
1123	return
1124}
1125
1126//======================================================================
1127
1128// BackgroundColor is an ICellStyler that expresses a specific background color and no preference for
1129// foreground color or style.
1130type BackgroundColor struct {
1131	IColor
1132}
1133
1134var _ ICellStyler = (*BackgroundColor)(nil)
1135
1136func MakeBackground(c IColor) BackgroundColor {
1137	return BackgroundColor{c}
1138}
1139
1140// GetStyle implements ICellStyler.
1141func (a BackgroundColor) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
1142	x = NoColor{}
1143	y = a.IColor
1144	z = StyleNone
1145	return
1146}
1147
1148//======================================================================
1149
1150// StyledAs is an ICellStyler that expresses a specific text style and no preference for
1151// foreground and background color.
1152type StyledAs struct {
1153	StyleAttrs
1154}
1155
1156var _ ICellStyler = (*StyledAs)(nil)
1157
1158func MakeStyledAs(s StyleAttrs) StyledAs {
1159	return StyledAs{s}
1160}
1161
1162// GetStyle implements ICellStyler.
1163func (a StyledAs) GetStyle(prov IRenderContext) (x IColor, y IColor, z StyleAttrs) {
1164	x = NoColor{}
1165	y = NoColor{}
1166	z = a.StyleAttrs
1167	return
1168}
1169
1170//======================================================================
1171
1172// Palette implements IPalette and is a trivial implementation of a type that can store
1173// cell stylers and provide access to them via iteration.
1174type Palette map[string]ICellStyler
1175
1176var _ IPalette = (*Palette)(nil)
1177
1178// CellStyler will return an ICellStyler by name, if it exists.
1179func (m Palette) CellStyler(name string) (ICellStyler, bool) {
1180	i, ok := m[name]
1181	return i, ok
1182}
1183
1184// RangeOverPalette applies the supplied function to each member of the
1185// palette. If the function returns false, the loop terminates early.
1186func (m Palette) RangeOverPalette(f func(k string, v ICellStyler) bool) {
1187	for k, v := range m {
1188		if !f(k, v) {
1189			break
1190		}
1191	}
1192}
1193
1194//======================================================================
1195
1196// IColorToTCell is a utility function that will convert an IColor to a TCellColor
1197// in preparation for passing to tcell to render; if the conversion fails, a default
1198// TCellColor is returned (provided to the function via a parameter)
1199func IColorToTCell(color IColor, def TCellColor, mode ColorMode) TCellColor {
1200	res := def
1201	colTC, ok := color.ToTCellColor(mode) // Is there a color specified affirmatively? (i.e. not NoColor)
1202	if ok && colTC != ColorNone {         // Yes a color specified
1203		res = colTC
1204	}
1205	return res
1206}
1207
1208//======================================================================
1209// Local Variables:
1210// mode: Go
1211// fill-column: 110
1212// End:
1213