...
1 package termenv
2
3 import (
4 "errors"
5 "fmt"
6 "math"
7 "strings"
8
9 "github.com/lucasb-eyer/go-colorful"
10 )
11
12 var (
13
14 ErrInvalidColor = errors.New("invalid color")
15 )
16
17
18 const (
19 Foreground = "38"
20 Background = "48"
21 )
22
23
24
25 type Color interface {
26
27 Sequence(bg bool) string
28 }
29
30
31 type NoColor struct{}
32
33 func (c NoColor) String() string {
34 return ""
35 }
36
37
38 type ANSIColor int
39
40 func (c ANSIColor) String() string {
41 return ansiHex[c]
42 }
43
44
45 type ANSI256Color int
46
47 func (c ANSI256Color) String() string {
48 return ansiHex[c]
49 }
50
51
52 type RGBColor string
53
54
55 func ConvertToRGB(c Color) colorful.Color {
56 var hex string
57 switch v := c.(type) {
58 case RGBColor:
59 hex = string(v)
60 case ANSIColor:
61 hex = ansiHex[v]
62 case ANSI256Color:
63 hex = ansiHex[v]
64 }
65
66 ch, _ := colorful.Hex(hex)
67 return ch
68 }
69
70
71 func (c NoColor) Sequence(_ bool) string {
72 return ""
73 }
74
75
76 func (c ANSIColor) Sequence(bg bool) string {
77 col := int(c)
78 bgMod := func(c int) int {
79 if bg {
80 return c + 10
81 }
82 return c
83 }
84
85 if col < 8 {
86 return fmt.Sprintf("%d", bgMod(col)+30)
87 }
88 return fmt.Sprintf("%d", bgMod(col-8)+90)
89 }
90
91
92 func (c ANSI256Color) Sequence(bg bool) string {
93 prefix := Foreground
94 if bg {
95 prefix = Background
96 }
97 return fmt.Sprintf("%s;5;%d", prefix, c)
98 }
99
100
101 func (c RGBColor) Sequence(bg bool) string {
102 f, err := colorful.Hex(string(c))
103 if err != nil {
104 return ""
105 }
106
107 prefix := Foreground
108 if bg {
109 prefix = Background
110 }
111 return fmt.Sprintf("%s;2;%d;%d;%d", prefix, uint8(f.R*255), uint8(f.G*255), uint8(f.B*255))
112 }
113
114 func xTermColor(s string) (RGBColor, error) {
115 if len(s) < 24 || len(s) > 25 {
116 return RGBColor(""), ErrInvalidColor
117 }
118
119 switch {
120 case strings.HasSuffix(s, string(BEL)):
121 s = strings.TrimSuffix(s, string(BEL))
122 case strings.HasSuffix(s, string(ESC)):
123 s = strings.TrimSuffix(s, string(ESC))
124 case strings.HasSuffix(s, ST):
125 s = strings.TrimSuffix(s, ST)
126 default:
127 return RGBColor(""), ErrInvalidColor
128 }
129
130 s = s[4:]
131
132 prefix := ";rgb:"
133 if !strings.HasPrefix(s, prefix) {
134 return RGBColor(""), ErrInvalidColor
135 }
136 s = strings.TrimPrefix(s, prefix)
137
138 h := strings.Split(s, "/")
139 hex := fmt.Sprintf("#%s%s%s", h[0][:2], h[1][:2], h[2][:2])
140 return RGBColor(hex), nil
141 }
142
143 func ansi256ToANSIColor(c ANSI256Color) ANSIColor {
144 var r int
145 md := math.MaxFloat64
146
147 h, _ := colorful.Hex(ansiHex[c])
148 for i := 0; i <= 15; i++ {
149 hb, _ := colorful.Hex(ansiHex[i])
150 d := h.DistanceHSLuv(hb)
151
152 if d < md {
153 md = d
154 r = i
155 }
156 }
157
158 return ANSIColor(r)
159 }
160
161 func hexToANSI256Color(c colorful.Color) ANSI256Color {
162 v2ci := func(v float64) int {
163 if v < 48 {
164 return 0
165 }
166 if v < 115 {
167 return 1
168 }
169 return int((v - 35) / 40)
170 }
171
172
173 r := v2ci(c.R * 255.0)
174 g := v2ci(c.G * 255.0)
175 b := v2ci(c.B * 255.0)
176 ci := 36*r + 6*g + b
177
178
179 i2cv := [6]int{0, 0x5f, 0x87, 0xaf, 0xd7, 0xff}
180 cr := i2cv[r]
181 cg := i2cv[g]
182 cb := i2cv[b]
183
184
185 var grayIdx int
186 average := (r + g + b) / 3
187 if average > 238 {
188 grayIdx = 23
189 } else {
190 grayIdx = (average - 3) / 10
191 }
192 gv := 8 + 10*grayIdx
193
194
195 c2 := colorful.Color{R: float64(cr) / 255.0, G: float64(cg) / 255.0, B: float64(cb) / 255.0}
196 g2 := colorful.Color{R: float64(gv) / 255.0, G: float64(gv) / 255.0, B: float64(gv) / 255.0}
197 colorDist := c.DistanceHSLuv(c2)
198 grayDist := c.DistanceHSLuv(g2)
199
200 if colorDist <= grayDist {
201 return ANSI256Color(16 + ci)
202 }
203 return ANSI256Color(232 + grayIdx)
204 }
205
View as plain text