voidterm/termutil/theme.go

215 lines
4.0 KiB
Go

package termutil
import (
"fmt"
"image/color"
"strconv"
)
type Colour uint8
// See https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
const (
ColourBlack Colour = iota
ColourRed
ColourGreen
ColourYellow
ColourBlue
ColourMagenta
ColourCyan
ColourWhite
ColourBrightBlack
ColourBrightRed
ColourBrightGreen
ColourBrightYellow
ColourBrightBlue
ColourBrightMagenta
ColourBrightCyan
ColourBrightWhite
ColourBackground
ColourForeground
ColourSelectionBackground
ColourSelectionForeground
ColourCursorForeground
ColourCursorBackground
)
type Theme struct {
colourMap map[Colour]color.Color
}
var (
map4Bit = map[uint8]Colour{
30: ColourBlack,
31: ColourRed,
32: ColourGreen,
33: ColourYellow,
34: ColourBlue,
35: ColourMagenta,
36: ColourCyan,
37: ColourWhite,
90: ColourBrightBlack,
91: ColourBrightRed,
92: ColourBrightGreen,
93: ColourBrightYellow,
94: ColourBrightBlue,
95: ColourBrightMagenta,
96: ColourBrightCyan,
97: ColourBrightWhite,
40: ColourBlack,
41: ColourRed,
42: ColourGreen,
43: ColourYellow,
44: ColourBlue,
45: ColourMagenta,
46: ColourCyan,
47: ColourWhite,
100: ColourBrightBlack,
101: ColourBrightRed,
102: ColourBrightGreen,
103: ColourBrightYellow,
104: ColourBrightBlue,
105: ColourBrightMagenta,
106: ColourBrightCyan,
107: ColourBrightWhite,
}
)
func (t *Theme) ColourFrom4Bit(code uint8) color.Color {
colour, ok := map4Bit[code]
if !ok {
return color.Black
}
return t.colourMap[colour]
}
func (t *Theme) DefaultBackground() color.Color {
c, ok := t.colourMap[ColourBackground]
if !ok {
return color.RGBA{0, 0, 0, 0xff}
}
return c
}
func (t *Theme) DefaultForeground() color.Color {
c, ok := t.colourMap[ColourForeground]
if !ok {
return color.RGBA{255, 255, 255, 0xff}
}
return c
}
func (t *Theme) SelectionBackground() color.Color {
c, ok := t.colourMap[ColourSelectionBackground]
if !ok {
return color.RGBA{0, 0, 0, 0xff}
}
return c
}
func (t *Theme) SelectionForeground() color.Color {
c, ok := t.colourMap[ColourSelectionForeground]
if !ok {
return color.RGBA{255, 255, 255, 0xff}
}
return c
}
func (t *Theme) CursorBackground() color.Color {
c, ok := t.colourMap[ColourCursorBackground]
if !ok {
return color.RGBA{255, 255, 255, 0xff}
}
return c
}
func (t *Theme) CursorForeground() color.Color {
c, ok := t.colourMap[ColourCursorForeground]
if !ok {
return color.RGBA{0, 0, 0, 0xff}
}
return c
}
func (t *Theme) ColourFrom8Bit(n string) (color.Color, error) {
index, err := strconv.Atoi(n)
if err != nil {
return nil, err
}
if index < 16 {
return t.colourMap[Colour(index)], nil
}
if index >= 232 {
c := ((index - 232) * 0xff) / 0x18
return color.RGBA{
R: byte(c),
G: byte(c),
B: byte(c),
A: 0xff,
}, nil
}
var colour color.RGBA
colour.A = 0xff
indexR := ((index - 16) / 36)
if indexR > 0 {
colour.R = uint8(55 + indexR*40)
}
indexG := (((index - 16) % 36) / 6)
if indexG > 0 {
colour.G = uint8(55 + indexG*40)
}
indexB := ((index - 16) % 6)
if indexB > 0 {
colour.B = uint8(55 + indexB*40)
}
return colour, nil
}
func (t *Theme) ColourFrom24Bit(r, g, b string) (color.Color, error) {
ri, err := strconv.Atoi(r)
if err != nil {
return nil, err
}
gi, err := strconv.Atoi(g)
if err != nil {
return nil, err
}
bi, err := strconv.Atoi(b)
if err != nil {
return nil, err
}
return color.RGBA{
R: byte(ri),
G: byte(gi),
B: byte(bi),
A: 0xff,
}, nil
}
func (t *Theme) ColourFromAnsi(ansi []string, bg bool) (color.Color, error) {
if len(ansi) == 0 {
return nil, fmt.Errorf("invalid ansi colour code")
}
switch ansi[0] {
case "2":
if len(ansi) != 4 {
return nil, fmt.Errorf("invalid 24-bit ansi colour code")
}
return t.ColourFrom24Bit(ansi[1], ansi[2], ansi[3])
case "5":
if len(ansi) != 2 {
return nil, fmt.Errorf("invalid 8-bit ansi colour code")
}
return t.ColourFrom8Bit(ansi[1])
default:
return nil, fmt.Errorf("invalid ansi colour code")
}
}