399 lines
9.6 KiB
Go
399 lines
9.6 KiB
Go
//
|
|
// Copyright (c) 2016-2020 The Aurora Authors. All rights reserved.
|
|
// This program is free software. It comes without any warranty,
|
|
// to the extent permitted by applicable law. You can redistribute
|
|
// it and/or modify it under the terms of the Unlicense. See LICENSE
|
|
// file for more details or see below.
|
|
//
|
|
|
|
//
|
|
// This is free and unencumbered software released into the public domain.
|
|
//
|
|
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
// distribute this software, either in source code form or as a compiled
|
|
// binary, for any purpose, commercial or non-commercial, and by any
|
|
// means.
|
|
//
|
|
// In jurisdictions that recognize copyright laws, the author or authors
|
|
// of this software dedicate any and all copyright interest in the
|
|
// software to the public domain. We make this dedication for the benefit
|
|
// of the public at large and to the detriment of our heirs and
|
|
// successors. We intend this dedication to be an overt act of
|
|
// relinquishment in perpetuity of all present and future rights to this
|
|
// software under copyright law.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
// OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
// For more information, please refer to <http://unlicense.org/>
|
|
//
|
|
|
|
package aurora
|
|
|
|
// A Color type is a color. It can contain
|
|
// one background color, one foreground color
|
|
// and a format, including ideogram related
|
|
// formats.
|
|
type Color uint
|
|
|
|
/*
|
|
|
|
Developer note.
|
|
|
|
The int type is architecture depended and can be
|
|
represented as int32 or int64.
|
|
|
|
Thus, we can use 32-bits only to be fast and
|
|
cross-platform.
|
|
|
|
All supported formats requires 14 bits. It is
|
|
first 14 bits.
|
|
|
|
A foreground color requires 8 bit + 1 bit (presence flag).
|
|
And the same for background color.
|
|
|
|
The Color representations
|
|
|
|
[ bg 8 bit ] [fg 8 bit ] [ fg/bg 2 bits ] [ fm 14 bits ]
|
|
|
|
https://play.golang.org/p/fq2zcNstFoF
|
|
|
|
*/
|
|
|
|
// Special formats
|
|
const (
|
|
BoldFm Color = 1 << iota // 1
|
|
FaintFm // 2
|
|
ItalicFm // 3
|
|
UnderlineFm // 4
|
|
SlowBlinkFm // 5
|
|
RapidBlinkFm // 6
|
|
ReverseFm // 7
|
|
ConcealFm // 8
|
|
CrossedOutFm // 9
|
|
|
|
FrakturFm // 20
|
|
DoublyUnderlineFm // 21 or bold off for some systems
|
|
|
|
FramedFm // 51
|
|
EncircledFm // 52
|
|
OverlinedFm // 53
|
|
|
|
InverseFm = ReverseFm // alias to ReverseFm
|
|
BlinkFm = SlowBlinkFm // alias to SlowBlinkFm
|
|
HiddenFm = ConcealFm // alias to ConcealFm
|
|
StrikeThroughFm = CrossedOutFm // alias to CrossedOutFm
|
|
|
|
maskFm = BoldFm | FaintFm |
|
|
ItalicFm | UnderlineFm |
|
|
SlowBlinkFm | RapidBlinkFm |
|
|
ReverseFm |
|
|
ConcealFm | CrossedOutFm |
|
|
|
|
FrakturFm | DoublyUnderlineFm |
|
|
|
|
FramedFm | EncircledFm | OverlinedFm
|
|
|
|
flagFg Color = 1 << 14 // presence flag (14th bit)
|
|
flagBg Color = 1 << 15 // presence flag (15th bit)
|
|
|
|
shiftFg = 16 // shift for foreground (starting from 16th bit)
|
|
shiftBg = 24 // shift for background (starting from 24th bit)
|
|
)
|
|
|
|
// Foreground colors and related formats
|
|
const (
|
|
|
|
// 8 bits
|
|
|
|
// [ 0; 7] - 30-37
|
|
// [ 8; 15] - 90-97
|
|
// [ 16; 231] - RGB
|
|
// [232; 255] - grayscale
|
|
|
|
BlackFg Color = (iota << shiftFg) | flagFg // 30, 90
|
|
RedFg // 31, 91
|
|
GreenFg // 32, 92
|
|
YellowFg // 33, 93
|
|
BlueFg // 34, 94
|
|
MagentaFg // 35, 95
|
|
CyanFg // 36, 96
|
|
WhiteFg // 37, 97
|
|
|
|
BrightFg Color = ((1 << 3) << shiftFg) | flagFg // -> 90
|
|
|
|
// the BrightFg itself doesn't represent
|
|
// a color, thus it has not flagFg
|
|
|
|
// 5 bits
|
|
|
|
// BrownFg represents brown foreground color.
|
|
//
|
|
// Deprecated: use YellowFg instead, following specifications
|
|
BrownFg = YellowFg
|
|
|
|
//
|
|
maskFg = (0xff << shiftFg) | flagFg
|
|
)
|
|
|
|
// Background colors and related formats
|
|
const (
|
|
|
|
// 8 bits
|
|
|
|
// [ 0; 7] - 40-47
|
|
// [ 8; 15] - 100-107
|
|
// [ 16; 231] - RGB
|
|
// [232; 255] - grayscale
|
|
|
|
BlackBg Color = (iota << shiftBg) | flagBg // 40, 100
|
|
RedBg // 41, 101
|
|
GreenBg // 42, 102
|
|
YellowBg // 43, 103
|
|
BlueBg // 44, 104
|
|
MagentaBg // 45, 105
|
|
CyanBg // 46, 106
|
|
WhiteBg // 47, 107
|
|
|
|
BrightBg Color = ((1 << 3) << shiftBg) | flagBg // -> 100
|
|
|
|
// the BrightBg itself doesn't represent
|
|
// a color, thus it has not flagBg
|
|
|
|
// 5 bits
|
|
|
|
// BrownBg represents brown foreground color.
|
|
//
|
|
// Deprecated: use YellowBg instead, following specifications
|
|
BrownBg = YellowBg
|
|
|
|
//
|
|
maskBg = (0xff << shiftBg) | flagBg
|
|
)
|
|
|
|
const (
|
|
availFlags = "-+# 0"
|
|
esc = "\033["
|
|
clear = esc + "0m"
|
|
)
|
|
|
|
// IsValid returns true always
|
|
//
|
|
// Deprecated: don't use this method anymore
|
|
func (c Color) IsValid() bool {
|
|
return true
|
|
}
|
|
|
|
// Nos returns string like 1;7;31;45. It
|
|
// may be an empty string for empty color.
|
|
// If the zero is true, then the string
|
|
// is prepended with 0;
|
|
func (c Color) Nos(zero bool) string {
|
|
return string(c.appendNos(make([]byte, 0, 59), zero))
|
|
}
|
|
|
|
func appendCond(bs []byte, cond, semi bool, vals ...byte) []byte {
|
|
if !cond {
|
|
return bs
|
|
}
|
|
return appendSemi(bs, semi, vals...)
|
|
}
|
|
|
|
// if the semi is true, then prepend with semicolon
|
|
func appendSemi(bs []byte, semi bool, vals ...byte) []byte {
|
|
if semi {
|
|
bs = append(bs, ';')
|
|
}
|
|
return append(bs, vals...)
|
|
}
|
|
|
|
func itoa(t byte) string {
|
|
var (
|
|
a [3]byte
|
|
j = 2
|
|
)
|
|
for i := 0; i < 3; i, j = i+1, j-1 {
|
|
a[j] = '0' + t%10
|
|
if t = t / 10; t == 0 {
|
|
break
|
|
}
|
|
}
|
|
return string(a[j:])
|
|
}
|
|
|
|
func (c Color) appendFg(bs []byte, zero bool) []byte {
|
|
|
|
if zero || c&maskFm != 0 {
|
|
bs = append(bs, ';')
|
|
}
|
|
|
|
// 0- 7 : 30-37
|
|
// 8-15 : 90-97
|
|
// > 15 : 38;5;val
|
|
|
|
switch fg := (c & maskFg) >> shiftFg; {
|
|
case fg <= 7:
|
|
// '3' and the value itself
|
|
bs = append(bs, '3', '0'+byte(fg))
|
|
case fg <= 15:
|
|
// '9' and the value itself
|
|
bs = append(bs, '9', '0'+byte(fg&^0x08)) // clear bright flag
|
|
default:
|
|
bs = append(bs, '3', '8', ';', '5', ';')
|
|
bs = append(bs, itoa(byte(fg))...)
|
|
}
|
|
return bs
|
|
}
|
|
|
|
func (c Color) appendBg(bs []byte, zero bool) []byte {
|
|
|
|
if zero || c&(maskFm|maskFg) != 0 {
|
|
bs = append(bs, ';')
|
|
}
|
|
|
|
// 0- 7 : 40- 47
|
|
// 8-15 : 100-107
|
|
// > 15 : 48;5;val
|
|
|
|
switch fg := (c & maskBg) >> shiftBg; {
|
|
case fg <= 7:
|
|
// '3' and the value itself
|
|
bs = append(bs, '4', '0'+byte(fg))
|
|
case fg <= 15:
|
|
// '1', '0' and the value itself
|
|
bs = append(bs, '1', '0', '0'+byte(fg&^0x08)) // clear bright flag
|
|
default:
|
|
bs = append(bs, '4', '8', ';', '5', ';')
|
|
bs = append(bs, itoa(byte(fg))...)
|
|
}
|
|
return bs
|
|
}
|
|
|
|
func (c Color) appendFm9(bs []byte, zero bool) []byte {
|
|
|
|
bs = appendCond(bs, c&ItalicFm != 0,
|
|
zero || c&(BoldFm|FaintFm) != 0,
|
|
'3')
|
|
bs = appendCond(bs, c&UnderlineFm != 0,
|
|
zero || c&(BoldFm|FaintFm|ItalicFm) != 0,
|
|
'4')
|
|
// don't combine slow and rapid blink using only
|
|
// on of them, preferring slow blink
|
|
if c&SlowBlinkFm != 0 {
|
|
bs = appendSemi(bs,
|
|
zero || c&(BoldFm|FaintFm|ItalicFm|UnderlineFm) != 0,
|
|
'5')
|
|
} else if c&RapidBlinkFm != 0 {
|
|
bs = appendSemi(bs,
|
|
zero || c&(BoldFm|FaintFm|ItalicFm|UnderlineFm) != 0,
|
|
'6')
|
|
}
|
|
|
|
// including 1-2
|
|
const mask6i = BoldFm | FaintFm |
|
|
ItalicFm | UnderlineFm |
|
|
SlowBlinkFm | RapidBlinkFm
|
|
|
|
bs = appendCond(bs, c&ReverseFm != 0,
|
|
zero || c&(mask6i) != 0,
|
|
'7')
|
|
bs = appendCond(bs, c&ConcealFm != 0,
|
|
zero || c&(mask6i|ReverseFm) != 0,
|
|
'8')
|
|
bs = appendCond(bs, c&CrossedOutFm != 0,
|
|
zero || c&(mask6i|ReverseFm|ConcealFm) != 0,
|
|
'9')
|
|
|
|
return bs
|
|
}
|
|
|
|
// append 1;3;38;5;216 like string that represents ANSI
|
|
// color of the Color; the zero argument requires
|
|
// appending of '0' before to reset previous format
|
|
// and colors
|
|
func (c Color) appendNos(bs []byte, zero bool) []byte {
|
|
|
|
if zero {
|
|
bs = append(bs, '0') // reset previous
|
|
}
|
|
|
|
// formats
|
|
//
|
|
|
|
if c&maskFm != 0 {
|
|
|
|
// 1-2
|
|
|
|
// don't combine bold and faint using only on of them, preferring bold
|
|
|
|
if c&BoldFm != 0 {
|
|
bs = appendSemi(bs, zero, '1')
|
|
} else if c&FaintFm != 0 {
|
|
bs = appendSemi(bs, zero, '2')
|
|
}
|
|
|
|
// 3-9
|
|
|
|
const mask9 = ItalicFm | UnderlineFm |
|
|
SlowBlinkFm | RapidBlinkFm |
|
|
ReverseFm | ConcealFm | CrossedOutFm
|
|
|
|
if c&mask9 != 0 {
|
|
bs = c.appendFm9(bs, zero)
|
|
}
|
|
|
|
// 20-21
|
|
|
|
const (
|
|
mask21 = FrakturFm | DoublyUnderlineFm
|
|
mask9i = BoldFm | FaintFm | mask9
|
|
)
|
|
|
|
if c&mask21 != 0 {
|
|
bs = appendCond(bs, c&FrakturFm != 0,
|
|
zero || c&mask9i != 0,
|
|
'2', '0')
|
|
bs = appendCond(bs, c&DoublyUnderlineFm != 0,
|
|
zero || c&(mask9i|FrakturFm) != 0,
|
|
'2', '1')
|
|
}
|
|
|
|
// 50-53
|
|
|
|
const (
|
|
mask53 = FramedFm | EncircledFm | OverlinedFm
|
|
mask21i = mask9i | mask21
|
|
)
|
|
|
|
if c&mask53 != 0 {
|
|
bs = appendCond(bs, c&FramedFm != 0,
|
|
zero || c&mask21i != 0,
|
|
'5', '1')
|
|
bs = appendCond(bs, c&EncircledFm != 0,
|
|
zero || c&(mask21i|FramedFm) != 0,
|
|
'5', '2')
|
|
bs = appendCond(bs, c&OverlinedFm != 0,
|
|
zero || c&(mask21i|FramedFm|EncircledFm) != 0,
|
|
'5', '3')
|
|
}
|
|
|
|
}
|
|
|
|
// foreground
|
|
if c&maskFg != 0 {
|
|
bs = c.appendFg(bs, zero)
|
|
}
|
|
|
|
// background
|
|
if c&maskBg != 0 {
|
|
bs = c.appendBg(bs, zero)
|
|
}
|
|
|
|
return bs
|
|
}
|