1package color
2
3import (
4	"fmt"
5	"strconv"
6	"strings"
7)
8
9// 24 bit RGB color
10// RGB:
11// 	R 0-255 G 0-255 B 0-255
12// 	R 00-FF G 00-FF B 00-FF (16进制)
13//
14// Format:
15// 	ESC[ … 38;2;<r>;<g>;<b> … m // Select RGB foreground color
16// 	ESC[ … 48;2;<r>;<g>;<b> … m // Choose RGB background color
17//
18// links:
19// 	https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#24
20//
21// example:
22// 	fg: \x1b[38;2;30;144;255mMESSAGE\x1b[0m
23// 	bg: \x1b[48;2;30;144;255mMESSAGE\x1b[0m
24// 	both: \x1b[38;2;233;90;203;48;2;30;144;255mMESSAGE\x1b[0m
25const (
26	TplFgRGB = "38;2;%d;%d;%d"
27	TplBgRGB = "48;2;%d;%d;%d"
28	FgRGBPfx = "38;2;"
29	BgRGBPfx = "48;2;"
30)
31
32// mark color is fg or bg.
33const (
34	AsFg uint8 = iota
35	AsBg
36)
37
38// values from https://github.com/go-terminfo/terminfo
39// var (
40// RgbaBlack    = image_color.RGBA{0, 0, 0, 255}
41// Red       = color.RGBA{205, 0, 0, 255}
42// Green     = color.RGBA{0, 205, 0, 255}
43// Orange    = color.RGBA{205, 205, 0, 255}
44// Blue      = color.RGBA{0, 0, 238, 255}
45// Magenta   = color.RGBA{205, 0, 205, 255}
46// Cyan      = color.RGBA{0, 205, 205, 255}
47// LightGrey = color.RGBA{229, 229, 229, 255}
48//
49// DarkGrey     = color.RGBA{127, 127, 127, 255}
50// LightRed     = color.RGBA{255, 0, 0, 255}
51// LightGreen   = color.RGBA{0, 255, 0, 255}
52// Yellow       = color.RGBA{255, 255, 0, 255}
53// LightBlue    = color.RGBA{92, 92, 255, 255}
54// LightMagenta = color.RGBA{255, 0, 255, 255}
55// LightCyan    = color.RGBA{0, 255, 255, 255}
56// White        = color.RGBA{255, 255, 255, 255}
57// )
58
59/*************************************************************
60 * RGB Color(Bit24Color, TrueColor)
61 *************************************************************/
62
63// RGBColor definition.
64//
65// The first to third digits represent the color value.
66// The last digit represents the foreground(0), background(1), >1 is unset value
67//
68// Usage:
69// 	// 0, 1, 2 is R,G,B.
70// 	// 3rd: Fg=0, Bg=1, >1: unset value
71// 	RGBColor{30,144,255, 0}
72// 	RGBColor{30,144,255, 1}
73//
74// NOTICE: now support RGB color on windows CMD, PowerShell
75type RGBColor [4]uint8
76
77// create a empty RGBColor
78var emptyRGBColor = RGBColor{3: 99}
79
80// RGB color create.
81// Usage:
82// 	c := RGB(30,144,255)
83// 	c := RGB(30,144,255, true)
84// 	c.Print("message")
85func RGB(r, g, b uint8, isBg ...bool) RGBColor {
86	rgb := RGBColor{r, g, b}
87	if len(isBg) > 0 && isBg[0] {
88		rgb[3] = AsBg
89	}
90
91	return rgb
92}
93
94// Rgb alias of the RGB()
95func Rgb(r, g, b uint8, isBg ...bool) RGBColor { return RGB(r, g, b, isBg...) }
96
97// Bit24 alias of the RGB()
98func Bit24(r, g, b uint8, isBg ...bool) RGBColor { return RGB(r, g, b, isBg...) }
99
100// RGBFromSlice quick RGBColor from slice
101func RGBFromSlice(rgb []uint8, isBg ...bool) RGBColor {
102	return RGB(rgb[0], rgb[1], rgb[2], isBg...)
103}
104
105// HEX create RGB color from a HEX color string.
106// Usage:
107// 	c := HEX("ccc") // rgb: [204 204 204]
108// 	c := HEX("aabbcc") // rgb: [170 187 204]
109// 	c := HEX("#aabbcc")
110// 	c := HEX("0xaabbcc")
111// 	c.Print("message")
112func HEX(hex string, isBg ...bool) RGBColor {
113	if rgb := HexToRgb(hex); len(rgb) > 0 {
114		return RGB(uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]), isBg...)
115	}
116
117	// mark is empty
118	return emptyRGBColor
119}
120
121// Hex alias of the HEX()
122func Hex(hex string, isBg ...bool) RGBColor { return HEX(hex, isBg...) }
123
124// RGBFromString create RGB color from a string.
125// Usage:
126// 	c := RGBFromString("170,187,204")
127// 	c.Print("message")
128func RGBFromString(rgb string, isBg ...bool) RGBColor {
129	ss := stringToArr(rgb, ",")
130	if len(ss) != 3 {
131		return emptyRGBColor
132	}
133
134	var ar [3]int
135	for i, val := range ss {
136		iv, err := strconv.Atoi(val)
137		if err != nil {
138			return emptyRGBColor
139		}
140
141		ar[i] = iv
142	}
143
144	return RGB(uint8(ar[0]), uint8(ar[1]), uint8(ar[2]), isBg...)
145}
146
147// Set terminal by rgb/true color code
148func (c RGBColor) Set() error {
149	return SetTerminal(c.String())
150}
151
152// Reset terminal. alias of the ResetTerminal()
153func (c RGBColor) Reset() error {
154	return ResetTerminal()
155}
156
157// Print print message
158func (c RGBColor) Print(a ...interface{}) {
159	doPrintV2(c.String(), fmt.Sprint(a...))
160}
161
162// Printf format and print message
163func (c RGBColor) Printf(format string, a ...interface{}) {
164	doPrintV2(c.String(), fmt.Sprintf(format, a...))
165}
166
167// Println print message with newline
168func (c RGBColor) Println(a ...interface{}) {
169	doPrintlnV2(c.String(), a)
170}
171
172// Sprint returns rendered message
173func (c RGBColor) Sprint(a ...interface{}) string {
174	return RenderCode(c.String(), a...)
175}
176
177// Sprintf returns format and rendered message
178func (c RGBColor) Sprintf(format string, a ...interface{}) string {
179	return RenderString(c.String(), fmt.Sprintf(format, a...))
180}
181
182// Values to RGB values
183func (c RGBColor) Values() []int {
184	return []int{int(c[0]), int(c[1]), int(c[2])}
185}
186
187// Code to color code string without prefix. eg: "204;123;56"
188func (c RGBColor) Code() string {
189	return fmt.Sprintf("%d;%d;%d", c[0], c[1], c[2])
190}
191
192// Hex color rgb to hex string. as in "ff0080".
193func (c RGBColor) Hex() string {
194	return fmt.Sprintf("%02x%02x%02x", c[0], c[1], c[2])
195}
196
197// FullCode to color code string with prefix
198func (c RGBColor) FullCode() string {
199	return c.String()
200}
201
202// String to color code string with prefix. eg: "38;2;204;123;56"
203func (c RGBColor) String() string {
204	if c[3] == AsFg {
205		return fmt.Sprintf(TplFgRGB, c[0], c[1], c[2])
206	}
207
208	if c[3] == AsBg {
209		return fmt.Sprintf(TplBgRGB, c[0], c[1], c[2])
210	}
211
212	// c[3] > 1 is empty
213	return ""
214}
215
216// IsEmpty value
217func (c RGBColor) IsEmpty() bool {
218	return c[3] > AsBg
219}
220
221// IsValid value
222// func (c RGBColor) IsValid() bool {
223// 	return c[3] <= AsBg
224// }
225
226// C256 returns the closest approximate 256 (8 bit) color
227func (c RGBColor) C256() Color256 {
228	return C256(RgbTo256(c[0], c[1], c[2]), c[3] == AsBg)
229}
230
231// Basic returns the closest approximate 16 (4 bit) color
232func (c RGBColor) Basic() Color {
233	// return Color(RgbToAnsi(c[0], c[1], c[2], c[3] == AsBg))
234	return Color(Rgb2basic(c[0], c[1], c[2], c[3] == AsBg))
235}
236
237// Color returns the closest approximate 16 (4 bit) color
238func (c RGBColor) Color() Color { return c.Basic() }
239
240// C16 returns the closest approximate 16 (4 bit) color
241func (c RGBColor) C16() Color { return c.Basic() }
242
243/*************************************************************
244 * RGB Style
245 *************************************************************/
246
247// RGBStyle definition.
248//
249// Foreground/Background color
250// All are composed of 4 digits uint8, the first three digits are the color value;
251// The last bit is different from RGBColor, here it indicates whether the value is set.
252// - 1  Has been set
253// - ^1 Not set
254type RGBStyle struct {
255	// Name of the style
256	Name string
257	// color options of the style
258	opts Opts
259	// fg and bg color
260	fg, bg RGBColor
261}
262
263// NewRGBStyle create a RGBStyle.
264func NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle {
265	s := &RGBStyle{}
266	if len(bg) > 0 {
267		s.SetBg(bg[0])
268	}
269
270	return s.SetFg(fg)
271}
272
273// HEXStyle create a RGBStyle from HEX color string.
274// Usage:
275// 	s := HEXStyle("aabbcc", "eee")
276// 	s.Print("message")
277func HEXStyle(fg string, bg ...string) *RGBStyle {
278	s := &RGBStyle{}
279	if len(bg) > 0 {
280		s.SetBg(HEX(bg[0]))
281	}
282
283	if len(fg) > 0 {
284		s.SetFg(HEX(fg))
285	}
286
287	return s
288}
289
290// RGBStyleFromString create a RGBStyle from color value string.
291// Usage:
292// 	s := RGBStyleFromString("170,187,204", "70,87,4")
293// 	s.Print("message")
294func RGBStyleFromString(fg string, bg ...string) *RGBStyle {
295	s := &RGBStyle{}
296	if len(bg) > 0 {
297		s.SetBg(RGBFromString(bg[0]))
298	}
299
300	return s.SetFg(RGBFromString(fg))
301}
302
303// Set fg and bg color, can also with color options
304func (s *RGBStyle) Set(fg, bg RGBColor, opts ...Color) *RGBStyle {
305	return s.SetFg(fg).SetBg(bg).SetOpts(opts)
306}
307
308// SetFg set fg color
309func (s *RGBStyle) SetFg(fg RGBColor) *RGBStyle {
310	fg[3] = 1 // add fixed value, mark is valid
311	s.fg = fg
312	return s
313}
314
315// SetBg set bg color
316func (s *RGBStyle) SetBg(bg RGBColor) *RGBStyle {
317	bg[3] = 1 // add fixed value, mark is valid
318	s.bg = bg
319	return s
320}
321
322// SetOpts set color options
323func (s *RGBStyle) SetOpts(opts Opts) *RGBStyle {
324	s.opts = opts
325	return s
326}
327
328// AddOpts add options
329func (s *RGBStyle) AddOpts(opts ...Color) *RGBStyle {
330	s.opts.Add(opts...)
331	return s
332}
333
334// Print print message
335func (s *RGBStyle) Print(a ...interface{}) {
336	doPrintV2(s.String(), fmt.Sprint(a...))
337}
338
339// Printf format and print message
340func (s *RGBStyle) Printf(format string, a ...interface{}) {
341	doPrintV2(s.String(), fmt.Sprintf(format, a...))
342}
343
344// Println print message with newline
345func (s *RGBStyle) Println(a ...interface{}) {
346	doPrintlnV2(s.String(), a)
347}
348
349// Sprint returns rendered message
350func (s *RGBStyle) Sprint(a ...interface{}) string {
351	return RenderCode(s.String(), a...)
352}
353
354// Sprintf returns format and rendered message
355func (s *RGBStyle) Sprintf(format string, a ...interface{}) string {
356	return RenderString(s.String(), fmt.Sprintf(format, a...))
357}
358
359// Code convert to color code string
360func (s *RGBStyle) Code() string {
361	return s.String()
362}
363
364// FullCode convert to color code string
365func (s *RGBStyle) FullCode() string {
366	return s.String()
367}
368
369// String convert to color code string
370func (s *RGBStyle) String() string {
371	var ss []string
372	// last value ensure is enable.
373	if s.fg[3] == 1 {
374		ss = append(ss, fmt.Sprintf(TplFgRGB, s.fg[0], s.fg[1], s.fg[2]))
375	}
376
377	if s.bg[3] == 1 {
378		ss = append(ss, fmt.Sprintf(TplBgRGB, s.bg[0], s.bg[1], s.bg[2]))
379	}
380
381	if s.opts.IsValid() {
382		ss = append(ss, s.opts.String())
383	}
384
385	return strings.Join(ss, ";")
386}
387
388// IsEmpty style
389func (s *RGBStyle) IsEmpty() bool {
390	return s.fg[3] != 1 && s.bg[3] != 1
391}
392