├── logo.png ├── .github └── FUNDING.yml ├── go.mod ├── go.sum ├── scripts ├── README.md ├── gen.tmpl └── regenerate.go ├── options.go ├── LICENSE ├── README.md ├── ansi.go ├── ansi_test.go └── cols.go /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leaanthony/go-ansi-parser/HEAD/logo.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: leaanthony 4 | 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/leaanthony/go-ansi-parser 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/matryer/is v1.4.0 7 | github.com/rivo/uniseg v0.2.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 2 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 3 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 4 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 5 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | This script is used to regenerate the colour definitions used by go-ansi-parser. 2 | 3 | This script makes use of the colours defined [here](https://jonasjacek.github.io/colors/) which are licensed using the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/). -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | // ParseOption specifies parse option. 4 | type ParseOption struct { 5 | ignoreUnexpectedCode bool 6 | ansiForegroundColor string 7 | ansiBackgroundColor string 8 | } 9 | 10 | // WithIgnoreInvalidCodes disables returning an error on invalid ANSI code. 11 | func WithIgnoreInvalidCodes() ParseOption { 12 | return ParseOption{ignoreUnexpectedCode: true} 13 | } 14 | 15 | // WithDefaultForegroundColor specifies default foreground code (ANSI 39). 16 | // See ColourMap variable and foreground color codes 30-37. 17 | func WithDefaultForegroundColor(ansiColor string) ParseOption { 18 | return ParseOption{ansiForegroundColor: ansiColor} 19 | } 20 | 21 | // WithDefaultBackgroundColor specifies default foreground code (ANSI 49). 22 | // See ColourMap variable and foreground color codes 30-37. 23 | func WithDefaultBackgroundColor(ansiColor string) ParseOption { 24 | return ParseOption{ansiBackgroundColor: ansiColor} 25 | } 26 | -------------------------------------------------------------------------------- /scripts/gen.tmpl: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | // Rgb represents an RGB colour value 4 | // with 8bits per channel 5 | type Rgb struct { 6 | R uint8 `json:"r"` 7 | G uint8 `json:"g"` 8 | B uint8 `json:"b"` 9 | } 10 | 11 | // Hsl represents the HSL value of the colour 12 | type Hsl struct { 13 | H float64 `json:"h"` 14 | S float64 `json:"s"` 15 | L float64 `json:"l"` 16 | } 17 | 18 | // Col represents a colour value. 19 | // The Id is the ANSI colour ID. 20 | type Col struct { 21 | Id int `json:"id"` 22 | Hex string `json:"hex"` 23 | Rgb Rgb `json:"rgb"` 24 | Hsl Hsl `json:"hsl"` 25 | Name string `json:"name"` 26 | } 27 | 28 | // Cols represents the default colour definitions 29 | // used by the library. This may be overridden. 30 | var Cols = []*Col{ {{range $col := .}} 31 | { 32 | Id: {{.Id}}, 33 | Hex: "{{.Hex}}", 34 | Rgb: Rgb{ {{.Rgb.R}}, {{.Rgb.G}}, {{.Rgb.B}} }, 35 | Hsl: Hsl{ {{.Hsl.H}}, {{.Hsl.S}}, {{.Hsl.L}} }, 36 | Name: "{{.Name}}", 37 | },{{end}} 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-Present Lea Anthony 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/regenerate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "encoding/json" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | "text/template" 13 | ) 14 | 15 | // Rgb represents an RGB colour value 16 | // with 8bits per channel 17 | type Rgb struct { 18 | R uint8 `json:"r"` 19 | G uint8 `json:"g"` 20 | B uint8 `json:"b"` 21 | } 22 | 23 | // Hsl represents the HSL value of the colour 24 | type Hsl struct { 25 | H float64 `json:"h"` 26 | S float64 `json:"s"` 27 | L float64 `json:"l"` 28 | } 29 | 30 | // InputCol represents a colour value. 31 | type InputCol struct { 32 | Id uint8 `json:"colorId"` 33 | Hex string `json:"hexString"` 34 | Rgb Rgb `json:"rgb"` 35 | Hsl Hsl `json:"hsl"` 36 | Name string `json:"name"` 37 | } 38 | 39 | // Template is our cols.go template 40 | //go:embed gen.tmpl 41 | var Template string 42 | 43 | func main() { 44 | 45 | var Cols []InputCol 46 | 47 | resp, err := http.Get("https://jonasjacek.github.io/colors/data.json") 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | defer func() { 52 | err := resp.Body.Close() 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | }() 57 | data, err := ioutil.ReadAll(resp.Body) 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | err = json.Unmarshal(data, &Cols) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | 66 | t, err := template.New("cols").Parse(Template) 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | var buffer bytes.Buffer 71 | err = t.Execute(&buffer, Cols) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | err = os.WriteFile(filepath.Join("..", "cols.go"), buffer.Bytes(), 0755) 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 |
4 |

5 |

6 | A library for parsing ANSI encoded strings

7 | 8 | 9 | 10 | CodeFactor 11 | 12 |

13 | 14 | Go ANSI Parser converts strings with [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code) 15 | into a slice of structs that represent styled text. Features: 16 | 17 | * Can parse ANSI 16, 256 and TrueColor 18 | * Supports all styles: Regular, Bold, Faint, Italic, Blinking, Inversed, Invisible, Underlined, Strikethrough 19 | * Provides RGB, Hex, HSL, ANSI ID and Name for parsed colours 20 | * Truncation - works with emojis and grapheme clusters 21 | * Length - works with emojis and grapheme clusters 22 | * Cleanse - removes the ansi escape codes 23 | * Configurable colour map for customisation 24 | * 100% Test Coverage 25 | 26 | # Installation 27 | ```shell 28 | go get github.com/leaanthony/go-ansi-parser 29 | ``` 30 | 31 | ## Usage 32 | 33 | ### Parse 34 | ```go 35 | text, err := ansi.Parse("\u001b[1;31;40mHello World\033[0m") 36 | 37 | // is the equivalent of... 38 | 39 | text := []*ansi.StyledText{ 40 | { 41 | Label: "Hello World", 42 | FgCol: &ansi.Col{ 43 | Id: 9, 44 | Hex: "#ff0000", 45 | Rgb: &ansi.Rgb{ R: 255, G: 0, B: 0 }, 46 | Hsl: &ansi.Hsl{ H: 0, S: 100, L: 50 }, 47 | Name: "Red", 48 | }, 49 | BgCol: &ansi.Col{ 50 | Id: 0, 51 | Hex: "#000000", 52 | Rgb: &ansi.Rgb{0, 0, 0}, 53 | Hsl: &ansi.Hsl{0, 0, 0}, 54 | Name: "Black", 55 | }, 56 | Style: 1, 57 | }, 58 | } 59 | ``` 60 | ### Truncating 61 | ```go 62 | shorter, err := ansi.Truncate("\u001b[1;31;40mHello\033[0m \u001b[0;30mWorld!\033[0m", 8) 63 | 64 | // is the equivalent of... 65 | 66 | shorter := "\u001b[1;31;40mHello\033[0m \u001b[0;30mWo\033[0m" 67 | ``` 68 | ### Cleanse 69 | ```go 70 | cleaner, err := ansi.Cleanse("\u001b[1;31;40mHello\033[0m \u001b[0;30mWorld!\033[0m") 71 | 72 | // is the equivalent of... 73 | 74 | cleaner := "Hello World!" 75 | ``` 76 | ### Length 77 | ```go 78 | length, err := ansi.Length("\u001b[1;31;40mHello\033[0m \u001b[0;30mWorld!\033[0m") 79 | 80 | // is the equivalent of... 81 | 82 | length := 12 83 | 84 | // Works with grapheme clusters and emoji 85 | length, err := ansi.Length("\u001b[1;31;40m👩🏽‍🔧😎\033[0m") // 2 86 | ``` 87 | -------------------------------------------------------------------------------- /ansi.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/rivo/uniseg" 9 | ) 10 | 11 | // TextStyle is a type representing the 12 | // ansi text styles 13 | type TextStyle int 14 | 15 | const ( 16 | // Bold Style 17 | Bold TextStyle = 1 << 0 18 | // Faint Style 19 | Faint TextStyle = 1 << 1 20 | // Italic Style 21 | Italic TextStyle = 1 << 2 22 | // Blinking Style 23 | Blinking TextStyle = 1 << 3 24 | // Inversed Style 25 | Inversed TextStyle = 1 << 4 26 | // Invisible Style 27 | Invisible TextStyle = 1 << 5 28 | // Underlined Style 29 | Underlined TextStyle = 1 << 6 30 | // Strikethrough Style 31 | Strikethrough TextStyle = 1 << 7 32 | // Bright Style 33 | Bright TextStyle = 1 << 8 34 | ) 35 | 36 | type ColourMode int 37 | 38 | const ( 39 | Default ColourMode = 0 40 | TwoFiveSix ColourMode = 1 41 | TrueColour ColourMode = 2 42 | ) 43 | 44 | var invalid = fmt.Errorf("invalid ansi string") 45 | var missingTerminator = fmt.Errorf("missing escape terminator 'm'") 46 | var invalidTrueColorSequence = fmt.Errorf("invalid TrueColor sequence") 47 | var invalid256ColSequence = fmt.Errorf("invalid 256 colour sequence") 48 | 49 | const ( 50 | // Default colors uses foreground color codes [30-37]. 51 | // See ColourMap and case for background colors. 52 | defaultForegroundColor = "37" 53 | defaultBackgroundColor = "30" 54 | ) 55 | 56 | // StyledText represents a single formatted string 57 | type StyledText struct { 58 | Label string 59 | FgCol *Col 60 | BgCol *Col 61 | Style TextStyle 62 | ColourMode ColourMode 63 | // Offset is the offset into the input string where the StyledText begins 64 | Offset int 65 | // Len is the length in bytes of the substring of the input text that 66 | // contains the styled text 67 | Len int 68 | } 69 | 70 | func (s *StyledText) styleToParams() []string { 71 | var params []string 72 | if s.Bold() { 73 | params = append(params, "1") 74 | } 75 | if s.Faint() { 76 | params = append(params, "2") 77 | } 78 | if s.Italic() { 79 | params = append(params, "3") 80 | } 81 | if s.Underlined() { 82 | params = append(params, "4") 83 | } 84 | if s.Blinking() { 85 | params = append(params, "5") 86 | } 87 | if s.Inversed() { 88 | params = append(params, "7") 89 | } 90 | if s.Invisible() { 91 | params = append(params, "8") 92 | } 93 | if s.Strikethrough() { 94 | params = append(params, "9") 95 | } 96 | if s.FgCol != nil { 97 | // Do we have an ID? 98 | switch s.ColourMode { 99 | case Default: 100 | offset := 30 101 | id := s.FgCol.Id 102 | // Adjust when bold has been applied to the id 103 | if (s.Bold() || s.Bright()) && id > 7 && id < 16 { 104 | id -= 8 105 | } 106 | if s.Bright() { 107 | offset = 90 108 | } 109 | params = append(params, fmt.Sprintf("%d", id+offset)) 110 | case TwoFiveSix: 111 | params = append(params, []string{"38", "5", fmt.Sprintf("%d", s.FgCol.Id)}...) 112 | case TrueColour: 113 | r := fmt.Sprintf("%d", s.FgCol.Rgb.R) 114 | g := fmt.Sprintf("%d", s.FgCol.Rgb.G) 115 | b := fmt.Sprintf("%d", s.FgCol.Rgb.B) 116 | params = append(params, []string{"38", "2", r, g, b}...) 117 | } 118 | } 119 | if s.BgCol != nil { 120 | // Do we have an ID? 121 | switch s.ColourMode { 122 | case Default: 123 | id := s.BgCol.Id 124 | offset := 40 125 | if s.Bright() { 126 | offset = 100 127 | } 128 | // Adjust when bold has been applied to the id 129 | if (s.Bold() || s.Bright()) && id > 7 && id < 16 { 130 | id -= 8 131 | } 132 | params = append(params, fmt.Sprintf("%d", id+offset)) 133 | case TwoFiveSix: 134 | params = append(params, []string{"48", "5", fmt.Sprintf("%d", s.BgCol.Id)}...) 135 | case TrueColour: 136 | r := fmt.Sprintf("%d", s.BgCol.Rgb.R) 137 | g := fmt.Sprintf("%d", s.BgCol.Rgb.G) 138 | b := fmt.Sprintf("%d", s.BgCol.Rgb.B) 139 | params = append(params, []string{"48", "2", r, g, b}...) 140 | } 141 | } 142 | return params 143 | } 144 | 145 | func (s *StyledText) String() string { 146 | params := strings.Join(s.styleToParams(), ";") 147 | return "\033[0;" + params + "m" + s.Label + "\033[0m" 148 | } 149 | 150 | // Bold will return true if the text has a Bold style 151 | func (s *StyledText) Bold() bool { 152 | return s.Style&Bold == Bold 153 | } 154 | 155 | // Faint will return true if the text has a Faint style 156 | func (s *StyledText) Faint() bool { 157 | return s.Style&Faint == Faint 158 | } 159 | 160 | // Italic will return true if the text has an Italic style 161 | func (s *StyledText) Italic() bool { 162 | return s.Style&Italic == Italic 163 | } 164 | 165 | // Blinking will return true if the text has a Blinking style 166 | func (s *StyledText) Blinking() bool { 167 | return s.Style&Blinking == Blinking 168 | } 169 | 170 | // Inversed will return true if the text has an Inversed style 171 | func (s *StyledText) Inversed() bool { 172 | return s.Style&Inversed == Inversed 173 | } 174 | 175 | // Invisible will return true if the text has an Invisible style 176 | func (s *StyledText) Invisible() bool { 177 | return s.Style&Invisible == Invisible 178 | } 179 | 180 | // Underlined will return true if the text has an Underlined style 181 | func (s *StyledText) Underlined() bool { 182 | return s.Style&Underlined == Underlined 183 | } 184 | 185 | // Strikethrough will return true if the text has a Strikethrough style 186 | func (s *StyledText) Strikethrough() bool { 187 | return s.Style&Strikethrough == Strikethrough 188 | } 189 | 190 | // Bright will return true if the text has a Bright style 191 | func (s *StyledText) Bright() bool { 192 | return s.Style&Bright == Bright 193 | } 194 | 195 | // ColourMap maps ansi identifiers to a colour 196 | var ColourMap = map[string]map[string]*Col{ 197 | "Regular": { 198 | "30": Cols[0], 199 | "31": Cols[1], 200 | "32": Cols[2], 201 | "33": Cols[3], 202 | "34": Cols[4], 203 | "35": Cols[5], 204 | "36": Cols[6], 205 | "37": Cols[7], 206 | "90": Cols[8], 207 | "91": Cols[9], 208 | "92": Cols[10], 209 | "93": Cols[11], 210 | "94": Cols[12], 211 | "95": Cols[13], 212 | "96": Cols[14], 213 | "97": Cols[15], 214 | "100": Cols[8], 215 | "101": Cols[9], 216 | "102": Cols[10], 217 | "103": Cols[11], 218 | "104": Cols[12], 219 | "105": Cols[13], 220 | "106": Cols[14], 221 | "107": Cols[15], 222 | }, 223 | "Bold": { 224 | "30": Cols[8], 225 | "31": Cols[9], 226 | "32": Cols[10], 227 | "33": Cols[11], 228 | "34": Cols[12], 229 | "35": Cols[13], 230 | "36": Cols[14], 231 | "37": Cols[15], 232 | "90": Cols[8], 233 | "91": Cols[9], 234 | "92": Cols[10], 235 | "93": Cols[11], 236 | "94": Cols[12], 237 | "95": Cols[13], 238 | "96": Cols[14], 239 | "97": Cols[15], 240 | "100": Cols[8], 241 | "101": Cols[9], 242 | "102": Cols[10], 243 | "103": Cols[11], 244 | "104": Cols[12], 245 | "105": Cols[13], 246 | "106": Cols[14], 247 | "107": Cols[15], 248 | }, 249 | "Faint": { 250 | "30": Cols[0], 251 | "31": Cols[1], 252 | "32": Cols[2], 253 | "33": Cols[3], 254 | "34": Cols[4], 255 | "35": Cols[5], 256 | "36": Cols[6], 257 | "37": Cols[7], 258 | }, 259 | } 260 | 261 | // Parse will convert an ansi encoded string and return 262 | // a slice of StyledText structs that represent the text. 263 | // If parsing is unsuccessful, an error is returned. 264 | func Parse(input string, options ...ParseOption) ([]*StyledText, error) { 265 | var result []*StyledText 266 | index := 0 267 | offset := 0 268 | escapeCodeLen := 0 269 | var currentStyledText = &StyledText{} 270 | 271 | if len(input) == 0 { 272 | return []*StyledText{currentStyledText}, nil 273 | } 274 | 275 | for { 276 | // Read all chars to next escape code 277 | esc := strings.Index(input, "\033[") 278 | 279 | // If no more esc chars, save what's left and return 280 | if esc == -1 { 281 | text := input[index:] 282 | if len(text) > 0 { 283 | currentStyledText.Label = text 284 | currentStyledText.Offset = offset 285 | currentStyledText.Len = len(text) + escapeCodeLen 286 | result = append(result, currentStyledText) 287 | } 288 | return result, nil 289 | } 290 | label := input[:esc] 291 | if len(label) > 0 { 292 | currentStyledText.Label = label 293 | currentStyledText.Offset = offset 294 | currentStyledText.Len = len(label) + escapeCodeLen 295 | offset += currentStyledText.Len 296 | result = append(result, currentStyledText) 297 | currentStyledText = &StyledText{ 298 | Label: "", 299 | FgCol: currentStyledText.FgCol, 300 | BgCol: currentStyledText.BgCol, 301 | Style: currentStyledText.Style, 302 | } 303 | escapeCodeLen = 0 304 | } 305 | input = input[esc:] 306 | // skip 307 | input = input[2:] 308 | 309 | // Read in params 310 | endesc := strings.Index(input, "m") 311 | if endesc == -1 { 312 | return nil, missingTerminator 313 | } 314 | paramText := input[:endesc] 315 | input = input[endesc+1:] 316 | escapeCodeLen += 2 + endesc + 1 317 | params := strings.Split(paramText, ";") 318 | colourMap := ColourMap["Regular"] 319 | skip := 0 320 | for index, param := range params { 321 | if skip > 0 { 322 | skip-- 323 | continue 324 | } 325 | param = stripLeadingZeros(param) 326 | switch param { 327 | case "0", "": 328 | colourMap = ColourMap["Regular"] 329 | currentStyledText.Style = 0 330 | currentStyledText.FgCol = nil 331 | currentStyledText.BgCol = nil 332 | case "1": 333 | // Bold 334 | colourMap = ColourMap["Bold"] 335 | currentStyledText.Style |= Bold 336 | case "2": 337 | // Dim/Feint 338 | colourMap = ColourMap["Faint"] 339 | currentStyledText.Style |= Faint 340 | case "3": 341 | // Italic 342 | currentStyledText.Style |= Italic 343 | case "4": 344 | // Underlined 345 | currentStyledText.Style |= Underlined 346 | case "5": 347 | // Blinking 348 | currentStyledText.Style |= Blinking 349 | case "7": 350 | // Inversed 351 | currentStyledText.Style |= Inversed 352 | case "8": 353 | // Invisible 354 | currentStyledText.Style |= Invisible 355 | case "9": 356 | // Strikethrough 357 | currentStyledText.Style |= Strikethrough 358 | case "30", "31", "32", "33", "34", "35", "36", "37": 359 | currentStyledText.FgCol = colourMap[param] 360 | case "90", "91", "92", "93", "94", "95", "96", "97": 361 | currentStyledText.FgCol = colourMap[param] 362 | currentStyledText.Style |= Bright 363 | case "100", "101", "102", "103", "104", "105", "106", "107": 364 | currentStyledText.BgCol = colourMap[param] 365 | currentStyledText.Style |= Bright 366 | case "40", "41", "42", "43", "44", "45", "46", "47": 367 | bgcol := "3" + param[1:] // Equivalent of -10 368 | currentStyledText.BgCol = colourMap[bgcol] 369 | case "38", "48": 370 | if len(params)-index < 3 { 371 | return nil, invalid 372 | } 373 | // 256 colours 374 | param1 := stripLeadingZeros(params[index+1]) 375 | if param1 == "5" { 376 | skip = 2 377 | colIndexText := stripLeadingZeros(params[index+2]) 378 | colIndex, err := strconv.Atoi(colIndexText) 379 | if err != nil { 380 | return nil, invalid256ColSequence 381 | } 382 | if colIndex < 0 || colIndex > 255 { 383 | return nil, invalid256ColSequence 384 | } 385 | currentStyledText.ColourMode = TwoFiveSix 386 | if param == "38" { 387 | currentStyledText.FgCol = Cols[colIndex] 388 | continue 389 | } 390 | currentStyledText.BgCol = Cols[colIndex] 391 | continue 392 | } 393 | // we must have 4 params left 394 | if len(params)-index < 5 { 395 | return nil, invalidTrueColorSequence 396 | } 397 | if param1 != "2" { 398 | return nil, invalidTrueColorSequence 399 | } 400 | var r, g, b uint8 401 | ri, err := strconv.Atoi(params[index+2]) 402 | if err != nil { 403 | return nil, invalidTrueColorSequence 404 | } 405 | gi, err := strconv.Atoi(params[index+3]) 406 | if err != nil { 407 | return nil, invalidTrueColorSequence 408 | } 409 | bi, err := strconv.Atoi(params[index+4]) 410 | if err != nil { 411 | return nil, invalidTrueColorSequence 412 | } 413 | if bi > 255 || gi > 255 || ri > 255 { 414 | return nil, invalidTrueColorSequence 415 | } 416 | if bi < 0 || gi < 0 || ri < 0 { 417 | return nil, invalidTrueColorSequence 418 | } 419 | r = uint8(ri) 420 | g = uint8(gi) 421 | b = uint8(bi) 422 | skip = 4 423 | colvalue := fmt.Sprintf("#%02x%02x%02x", r, g, b) 424 | currentStyledText.ColourMode = TrueColour 425 | if param == "38" { 426 | currentStyledText.FgCol = &Col{Id: 256, Hex: colvalue, Rgb: Rgb{r, g, b}} 427 | continue 428 | } 429 | currentStyledText.BgCol = &Col{Id: 256, Hex: colvalue, Rgb: Rgb{r, g, b}} 430 | case "39": 431 | // Lookup for default foreground color. 432 | foregroundColor := colourMap[defaultForegroundColor] 433 | for _, option := range options { 434 | if option.ansiForegroundColor != "" { 435 | foregroundColor = colourMap[option.ansiForegroundColor] 436 | break 437 | } 438 | } 439 | 440 | // Set selected foreground color. 441 | currentStyledText.FgCol = foregroundColor 442 | case "49": 443 | // Lookup for default background color. 444 | backgroundColor := colourMap[defaultBackgroundColor] 445 | for _, option := range options { 446 | if option.ansiBackgroundColor != "" { 447 | backgroundColor = colourMap[option.ansiBackgroundColor] 448 | break 449 | } 450 | } 451 | 452 | // Set selected background color. 453 | currentStyledText.BgCol = backgroundColor 454 | default: 455 | // Unexpected codes may be ignored. 456 | unexpectedCodeIgnored := false 457 | for _, option := range options { 458 | if option.ignoreUnexpectedCode { 459 | unexpectedCodeIgnored = true 460 | break 461 | } 462 | } 463 | if !unexpectedCodeIgnored { 464 | return nil, invalid 465 | } 466 | } 467 | } 468 | } 469 | } 470 | 471 | func stripLeadingZeros(s string) string { 472 | if len(s) < 2 { 473 | return s 474 | } 475 | return strings.TrimLeft(s, "0") 476 | } 477 | 478 | // HasEscapeCodes tests that input has escape codes. 479 | func HasEscapeCodes(input string) bool { 480 | return strings.IndexAny(input, "\033[") != -1 481 | } 482 | 483 | // String builds an ANSI string for specified StyledText slice. 484 | func String(input []*StyledText) string { 485 | var result strings.Builder 486 | for _, text := range input { 487 | params := text.styleToParams() 488 | if len(params) == 0 { 489 | result.WriteString(text.Label) 490 | continue 491 | } 492 | result.WriteString(text.String()) 493 | } 494 | return result.String() 495 | } 496 | 497 | // Truncate truncates text to length but preserves control symbols in ANSI string. 498 | func Truncate(input string, maxChars int, options ...ParseOption) (string, error) { 499 | parsed, err := Parse(input, options...) 500 | if err != nil { 501 | return "", err 502 | } 503 | charsLeft := maxChars 504 | var result []*StyledText 505 | for _, element := range parsed { 506 | userPerceivedChars := uniseg.GraphemeClusterCount(element.Label) 507 | if userPerceivedChars >= charsLeft { 508 | var newLabel []rune 509 | graphemes := uniseg.NewGraphemes(element.Label) 510 | for graphemes.Next() { 511 | newLabel = append(newLabel, graphemes.Runes()...) 512 | charsLeft-- 513 | if charsLeft == 0 { 514 | element.Label = string(newLabel) 515 | result = append(result, element) 516 | return String(result), nil 517 | } 518 | } 519 | } 520 | result = append(result, element) 521 | charsLeft -= userPerceivedChars 522 | } 523 | return String(result), nil 524 | } 525 | 526 | // Cleanse removes ANSI control symbols from the string. 527 | func Cleanse(input string, options ...ParseOption) (string, error) { 528 | if input == "" { 529 | return "", nil 530 | } 531 | parsed, err := Parse(input, options...) 532 | if err != nil { 533 | return "", err 534 | } 535 | var result strings.Builder 536 | for _, element := range parsed { 537 | result.WriteString(element.Label) 538 | } 539 | return result.String(), nil 540 | } 541 | 542 | // Length calculates count of user-perceived characters in ANSI string. 543 | func Length(input string, options ...ParseOption) (int, error) { 544 | if input == "" { 545 | return 0, nil 546 | } 547 | parsed, err := Parse(input, options...) 548 | if err != nil { 549 | return -1, err 550 | } 551 | var result int 552 | for _, element := range parsed { 553 | userPerceivedChars := uniseg.GraphemeClusterCount(element.Label) 554 | result += userPerceivedChars 555 | } 556 | return result, nil 557 | } 558 | -------------------------------------------------------------------------------- /ansi_test.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "testing" 5 | 6 | is "github.com/matryer/is" 7 | ) 8 | 9 | func TestParseAnsi16Styles(t *testing.T) { 10 | is2 := is.New(t) 11 | var got []*StyledText 12 | var err error 13 | 14 | // Bold 15 | got, err = Parse("\u001b[1;30mHello World\033[0m") 16 | is2.NoErr(err) 17 | is2.Equal(len(got), 1) 18 | is2.True(got[0].Bold()) 19 | is2.Equal(got[0].Len, 18) 20 | is2.Equal(got[0].Offset, 0) 21 | // Faint 22 | got, err = Parse("\u001b[2;30mHello World\033[0m") 23 | is2.NoErr(err) 24 | is2.Equal(len(got), 1) 25 | is2.True(got[0].Faint()) 26 | // Italic 27 | got, err = Parse("\u001b[3;30mHello World\033[0m") 28 | is2.NoErr(err) 29 | is2.Equal(len(got), 1) 30 | is2.True(got[0].Italic()) 31 | // Underlined 32 | got, err = Parse("\u001b[4;30mHello World\033[0m") 33 | is2.NoErr(err) 34 | is2.Equal(len(got), 1) 35 | is2.True(got[0].Underlined()) 36 | // Blinking 37 | got, err = Parse("\u001b[5;30mHello World\033[0m") 38 | is2.NoErr(err) 39 | is2.Equal(len(got), 1) 40 | is2.True(got[0].Blinking()) 41 | // Inversed 42 | got, err = Parse("\u001b[7;30mHello World\033[0m") 43 | is2.NoErr(err) 44 | is2.Equal(len(got), 1) 45 | is2.True(got[0].Inversed()) 46 | // Invisible 47 | got, err = Parse("\u001b[8;30mHello World\033[0m") 48 | is2.NoErr(err) 49 | is2.Equal(len(got), 1) 50 | is2.True(got[0].Invisible()) 51 | // Strikethrough 52 | got, err = Parse("\u001b[9;30mHello World\033[0m") 53 | is2.NoErr(err) 54 | is2.Equal(len(got), 1) 55 | is2.True(got[0].Strikethrough()) 56 | } 57 | 58 | func TestParseAnsi16Swap(t *testing.T) { 59 | is2 := is.New(t) 60 | var got []*StyledText 61 | var err error 62 | 63 | // Swap single 64 | c0ffee := &Col{ 65 | Id: 0, 66 | Hex: "c0ffee", 67 | Name: "Coffee", 68 | } 69 | original := ColourMap["Regular"]["30"] 70 | ColourMap["Regular"]["30"] = c0ffee 71 | got, err = Parse("\u001b[0;30mHello World\033[0m") 72 | is2.NoErr(err) 73 | is2.Equal(len(got), 1) 74 | is2.Equal(got[0].FgCol.Name, "Coffee") 75 | 76 | // Restore 77 | ColourMap["Regular"]["30"] = original 78 | got, err = Parse("\u001b[0;30mHello World\033[0m") 79 | is2.NoErr(err) 80 | is2.Equal(len(got), 1) 81 | is2.Equal(got[0].FgCol.Name, "Black") 82 | } 83 | 84 | func TestParseAnsi16SingleColour(t *testing.T) { 85 | is2 := is.New(t) 86 | tests := []struct { 87 | name string 88 | input string 89 | wantText string 90 | wantColor string 91 | wantErr bool 92 | }{ 93 | {"No formatting", "Hello World", "Hello World", "", false}, 94 | {"Black", "\u001b[0;30mHello World\033[0m", "Hello World", "Black", false}, 95 | {"Red", "\u001b[0;31mHello World\033[0m", "Hello World", "Maroon", false}, 96 | {"Green", "\u001b[0;32mGreen\033[0m", "Green", "Green", false}, 97 | {"Yellow", "\u001b[0;33m😀\033[0m", "😀", "Olive", false}, 98 | {"Blue", "\u001b[0;34m123\033[0m", "123", "Navy", false}, 99 | {"Purple", "\u001b[0;35m👩🏽‍🔧\u001B[0m", "👩🏽‍🔧", "Purple", false}, 100 | {"Cyan", "\033[0;36m😀\033[0m", "😀", "Teal", false}, 101 | {"White", "\u001b[0;37m[0;37m\033[0m", "[0;37m", "Silver", false}, 102 | {"Black Bold", "\u001b[1;30mHello World\033[0m", "Hello World", "Grey", false}, 103 | {"Red Bold", "\u001b[1;31mHello World\033[0m", "Hello World", "Red", false}, 104 | {"Green Bold", "\u001b[1;32mGreen\033[0m", "Green", "Lime", false}, 105 | {"Yellow Bold", "\u001b[1;33m😀\033[0m", "😀", "Yellow", false}, 106 | {"Blue Bold", "\u001b[1;34m123\033[0m", "123", "Blue", false}, 107 | {"Purple Bold", "\u001b[1;35m👩🏽‍🔧\u001B[0m", "👩🏽‍🔧", "Fuchsia", false}, 108 | {"Cyan Bold", "\033[1;36m😀\033[0m", "😀", "Aqua", false}, 109 | {"White Bold", "\u001b[1;37m[0;37m\033[0m", "[0;37m", "White", false}, 110 | {"Black Bold & Bright", "\u001b[1;90mHello World\033[0m", "Hello World", "Grey", false}, 111 | {"Red Bold & Bright", "\u001b[1;91mHello World\033[0m", "Hello World", "Red", false}, 112 | {"Green Bold & Bright", "\u001b[1;92mGreen\033[0m", "Green", "Lime", false}, 113 | {"Yellow Bold & Bright", "\u001b[1;93m😀\033[0m", "😀", "Yellow", false}, 114 | {"Blue Bold & Bright", "\u001b[1;94m123\033[0m", "123", "Blue", false}, 115 | {"Purple Bold & Bright", "\u001b[1;95m👩🏽‍🔧\u001B[0m", "👩🏽‍🔧", "Fuchsia", false}, 116 | {"Cyan Bold & Bright", "\033[1;96m😀\033[0m", "😀", "Aqua", false}, 117 | {"White Bold & Bright", "\u001b[1;97m[0;37m\033[0m", "[0;37m", "White", false}, 118 | {"Black Bright", "\u001b[90mHello World\033[0m", "Hello World", "Grey", false}, 119 | {"Red Bright", "\u001b[91mHello World\033[0m", "Hello World", "Red", false}, 120 | {"Green Bright", "\u001b[92mGreen\033[0m", "Green", "Lime", false}, 121 | {"Yellow Bright", "\u001b[93m😀\033[0m", "😀", "Yellow", false}, 122 | {"Blue Bright", "\u001b[94m123\033[0m", "123", "Blue", false}, 123 | {"Purple Bright", "\u001b[95m👩🏽‍🔧\u001B[0m", "👩🏽‍🔧", "Fuchsia", false}, 124 | {"Cyan Bright", "\033[96m😀\033[0m", "😀", "Aqua", false}, 125 | {"White Bright", "\u001b[97m[0;37m\033[0m", "[0;37m", "White", false}, 126 | {"Black Bold & Bright Background", "\u001b[1;100mHello World\033[0m", "Hello World", "Grey", false}, 127 | {"Red Bold & Bright Background", "\u001b[1;101mHello World\033[0m", "Hello World", "Red", false}, 128 | {"Green Bold & Bright Background", "\u001b[1;102mGreen\033[0m", "Green", "Lime", false}, 129 | {"Yellow Bold & Bright Background", "\u001b[1;103m😀\033[0m", "😀", "Yellow", false}, 130 | {"Blue Bold & Bright Background", "\u001b[1;104m123\033[0m", "123", "Blue", false}, 131 | {"Purple Bold & Bright Background", "\u001b[1;105m👩🏽‍🔧\u001B[0m", "👩🏽‍🔧", "Fuchsia", false}, 132 | {"Cyan Bold & Bright Background", "\033[1;106m😀\033[0m", "😀", "Aqua", false}, 133 | {"White Bold & Bright Background", "\u001b[1;107m[0;37m\033[0m", "[0;37m", "White", false}, 134 | {"Black Bright Background", "\u001b[100mHello World\033[0m", "Hello World", "Grey", false}, 135 | {"Red Bright Background", "\u001b[101mHello World\033[0m", "Hello World", "Red", false}, 136 | {"Green Bright Background", "\u001b[102mGreen\033[0m", "Green", "Lime", false}, 137 | {"Yellow Bright Background", "\u001b[103m😀\033[0m", "😀", "Yellow", false}, 138 | {"Blue Bright Background", "\u001b[104m123\033[0m", "123", "Blue", false}, 139 | {"Purple Bright Background", "\u001b[105m👩🏽‍🔧\u001B[0m", "👩🏽‍🔧", "Fuchsia", false}, 140 | {"Cyan Bright Background", "\033[106m😀\033[0m", "😀", "Aqua", false}, 141 | {"White Bright Background", "\u001b[107m[0;37m\033[0m", "[0;37m", "White", false}, 142 | 143 | {"Blank", "", "", "", false}, 144 | {"Emoji", "😀👩🏽‍🔧", "😀👩🏽‍🔧", "", false}, 145 | {"Spaces", " ", " ", "", false}, 146 | {"Bad code", "\u001b[1 ", "", "", true}, 147 | } 148 | for _, tt := range tests { 149 | t.Run(tt.name, func(t *testing.T) { 150 | got, err := Parse(tt.input) 151 | is2.Equal(err != nil, tt.wantErr) 152 | expectedLength := 1 153 | if tt.wantErr { 154 | expectedLength = 0 155 | } 156 | is2.Equal(len(got), expectedLength) 157 | if expectedLength == 1 { 158 | if len(tt.wantColor) > 0 { 159 | if got[0].FgCol != nil { 160 | is2.Equal(got[0].FgCol.Name, tt.wantColor) 161 | } else { 162 | is2.Equal(got[0].BgCol.Name, tt.wantColor) 163 | } 164 | } 165 | } 166 | }) 167 | } 168 | } 169 | 170 | func TestParseAnsi16SingleBGColour(t *testing.T) { 171 | is2 := is.New(t) 172 | tests := []struct { 173 | name string 174 | input string 175 | wantText string 176 | wantColor string 177 | wantErr bool 178 | }{ 179 | {"No formatting", "Hello World", "Hello World", "", false}, 180 | {"Black", "\u001b[0;40mHello World\033[0m", "Hello World", "Black", false}, 181 | {"Red", "\u001b[0;41mHello World\033[0m", "Hello World", "Maroon", false}, 182 | {"Green", "\u001b[0;42mGreen\033[0m", "Green", "Green", false}, 183 | {"Yellow", "\u001b[0;43m😀\033[0m", "😀", "Olive", false}, 184 | {"Blue", "\u001b[0;44m123\033[0m", "123", "Navy", false}, 185 | {"Purple", "\u001b[0;45m👩🏽‍🔧\u001B[0m", "👩🏽‍🔧", "Purple", false}, 186 | {"Cyan", "\033[0;46m😀\033[0m", "😀", "Teal", false}, 187 | {"White", "\u001b[0;47m[0;47m\033[0m", "[0;47m", "Silver", false}, 188 | {"Black Bold", "\u001b[1;40mHello World\033[0m", "Hello World", "Grey", false}, 189 | {"Red Bold", "\u001b[1;41mHello World\033[0m", "Hello World", "Red", false}, 190 | {"Green Bold", "\u001b[1;42mGreen\033[0m", "Green", "Lime", false}, 191 | {"Yellow Bold", "\u001b[1;43m😀\033[0m", "😀", "Yellow", false}, 192 | {"Blue Bold", "\u001b[1;44m123\033[0m", "123", "Blue", false}, 193 | {"Purple Bold", "\u001b[1;45m👩🏽‍🔧\u001B[0m", "👩🏽‍🔧", "Fuchsia", false}, 194 | {"Cyan Bold", "\033[1;46m😀\033[0m", "😀", "Aqua", false}, 195 | {"White Bold", "\u001b[1;47m[0;47m\033[0m", "[0;47m", "White", false}, 196 | {"Pre text", "Hello\u001b[0m", "Hello", "", false}, 197 | {"Blank", "", "", "", false}, 198 | {"Emoji", "😀👩🏽‍🔧", "😀👩🏽‍🔧", "", false}, 199 | {"Spaces", " ", " ", "", false}, 200 | {"Bad code", "\u001b[1 ", "", "", true}, 201 | {"No colour", "\033[m\033[40m \033[0m", " ", "", false}, 202 | } 203 | for _, tt := range tests { 204 | t.Run(tt.name, func(t *testing.T) { 205 | got, err := Parse(tt.input) 206 | is2.Equal(err != nil, tt.wantErr) 207 | expectedLength := 1 208 | if tt.wantErr { 209 | expectedLength = 0 210 | } 211 | is2.Equal(len(got), expectedLength) 212 | if expectedLength == 1 { 213 | if len(tt.wantColor) > 0 { 214 | is2.True(got[0].BgCol != nil) 215 | is2.Equal(got[0].BgCol.Name, tt.wantColor) 216 | } 217 | } 218 | }) 219 | } 220 | } 221 | 222 | func TestParseAnsi16MultiColour(t *testing.T) { 223 | is2 := is.New(t) 224 | tests := []struct { 225 | name string 226 | input string 227 | want []*StyledText 228 | wantErr bool 229 | }{ 230 | {"Black & Red", "\u001B[0;30mHello World\u001B[0m\u001B[0;31mHello World\u001B[0m", []*StyledText{ 231 | {Label: "Hello World", FgCol: &Col{Name: "Black"}, Offset: 0, Len: 18}, 232 | {Label: "Hello World", FgCol: &Col{Name: "Maroon"}, Offset: 18, Len: 22}, 233 | }, false}, 234 | {"Text then Black & Red", "This is great!\u001B[0;30mHello World\u001B[0m\u001B[0;31mHello World\u001B[0m", []*StyledText{ 235 | {Label: "This is great!", Offset: 0, Len: 14}, 236 | {Label: "Hello World", FgCol: &Col{Name: "Black"}, Offset: 14, Len: 18}, 237 | {Label: "Hello World", FgCol: &Col{Name: "Maroon"}, Offset: 32, Len: 22}, 238 | }, false}, 239 | {"Text Reset then Black & Red", "This is great!\u001B[0m\u001B[0;30mHello World\u001B[0m\u001B[0;31mHello World\u001B[0m", []*StyledText{ 240 | {Label: "This is great!", Offset: 0, Len: 14}, 241 | {Label: "Hello World", FgCol: &Col{Name: "Black"}, Offset: 14, Len: 22}, 242 | {Label: "Hello World", FgCol: &Col{Name: "Maroon"}, Offset: 36, Len: 22}, 243 | }, false}, 244 | {"Text Reset then Black & Red", "This is great!\u001B[0m", []*StyledText{ 245 | {Label: "This is great!", Offset: 0, Len: 14}, 246 | }, false}, 247 | {"Black & Red no reset", "\u001B[0;30mHello World\u001B[0;31mHello World", []*StyledText{ 248 | {Label: "Hello World", FgCol: &Col{Name: "Black"}, Offset: 0, Len: 18}, 249 | {Label: "Hello World", FgCol: &Col{Name: "Maroon"}, Offset: 18, Len: 18}, 250 | }, false}, 251 | {"Black,space,Red", "\u001B[0;30mHello World\u001B[0m \u001B[0;31mHello World\u001B[0m", []*StyledText{ 252 | {Label: "Hello World", FgCol: &Col{Name: "Black"}, Offset: 0, Len: 18}, 253 | {Label: " ", Offset: 18, Len: 5}, 254 | {Label: "Hello World", FgCol: &Col{Name: "Maroon"}, Offset: 23, Len: 18}, 255 | }, false}, 256 | {"Black,Red,Blue,Green underlined", "\033[4;30mBlack\u001B[0m\u001B[4;31mRed\u001B[0m\u001B[4;34mBlue\u001B[0m\u001B[4;32mGreen\u001B[0m", []*StyledText{ 257 | {Label: "Black", FgCol: &Col{Name: "Black"}, Style: Underlined, Offset: 0, Len: 12}, 258 | {Label: "Red", FgCol: &Col{Name: "Maroon"}, Style: Underlined, Offset: 12, Len: 14}, 259 | {Label: "Blue", FgCol: &Col{Name: "Navy"}, Style: Underlined, Offset: 26, Len: 15}, 260 | {Label: "Green", FgCol: &Col{Name: "Green"}, Style: Underlined, Offset: 41, Len: 16}, 261 | }, false}, 262 | {"Black,Red,Blue,Green bold", "\033[1;30mBlack\u001B[0m\u001B[1;31mRed\u001B[0m\u001B[1;34mBlue\u001B[0m\u001B[1;32mGreen\u001B[0m", []*StyledText{ 263 | {Label: "Black", FgCol: &Col{Name: "Grey"}, Style: Bold, Offset: 0, Len: 12}, 264 | {Label: "Red", FgCol: &Col{Name: "Red"}, Style: Bold, Offset: 12, Len: 14}, 265 | {Label: "Blue", FgCol: &Col{Name: "Blue"}, Style: Bold, Offset: 26, Len: 15}, 266 | {Label: "Green", FgCol: &Col{Name: "Lime"}, Style: Bold, Offset: 41, Len: 16}, 267 | }, false}, 268 | {"Green Feint & Yellow Italic", "\u001B[2;32m👩🏽‍🔧\u001B[0m\u001B[0;3;33m👩🏽‍🔧\u001B[0m", []*StyledText{ 269 | {Label: "👩🏽‍🔧", FgCol: &Col{Name: "Green"}, Style: Faint, Offset: 0, Len: len("👩🏽‍🔧") + 7}, 270 | {Label: "👩🏽‍🔧", FgCol: &Col{Name: "Olive"}, Style: Italic, Offset: len("👩🏽‍🔧") + 7, Len: len("👩🏽‍🔧") + 13}, 271 | }, false}, 272 | {"Green Blinking & Yellow Inversed", "\u001B[5;32m👩🏽‍🔧\u001B[0m\u001B[0;7;33m👩🏽‍🔧\u001B[0m", []*StyledText{ 273 | {Label: "👩🏽‍🔧", FgCol: &Col{Name: "Green"}, Style: Blinking, Offset: 0, Len: len("👩🏽‍🔧") + 7}, 274 | {Label: "👩🏽‍🔧", FgCol: &Col{Name: "Olive"}, Style: Inversed, Offset: len("👩🏽‍🔧") + 7, Len: len("👩🏽‍🔧") + 13}, 275 | }, false}, 276 | {"Green Invisible & Yellow Invisible & Strikethrough", "\u001B[8;32m👩🏽‍🔧\u001B[9;33m👩🏽‍🔧\u001B[0m", []*StyledText{ 277 | {Label: "👩🏽‍🔧", FgCol: &Col{Name: "Green"}, Style: Invisible, Offset: 0, Len: len("👩🏽‍🔧") + 7}, 278 | {Label: "👩🏽‍🔧", FgCol: &Col{Name: "Olive"}, Style: Invisible | Strikethrough, Offset: len("👩🏽‍🔧") + 7, Len: len("👩🏽‍🔧") + 7}, 279 | }, false}, 280 | {"Red Foregraound & Black Background", "\u001b[1;31;40mHello World\033[0m", []*StyledText{ 281 | {Label: "Hello World", FgCol: &Col{Name: "Red"}, BgCol: &Col{Name: "Black"}, Style: Bold, Offset: 0, Len: 21}, 282 | }, false}, 283 | } 284 | for _, tt := range tests { 285 | t.Run(tt.name, func(t *testing.T) { 286 | got, err := Parse(tt.input) 287 | is2.Equal(err != nil, tt.wantErr) 288 | for index, w := range tt.want { 289 | is2.Equal(got[index].Label, w.Label) 290 | if w.FgCol != nil { 291 | is2.Equal(got[index].FgCol.Name, w.FgCol.Name) 292 | } 293 | is2.Equal(got[index].Style, w.Style) 294 | is2.Equal(got[index].Offset, w.Offset) 295 | is2.Equal(got[index].Len, w.Len) 296 | } 297 | }) 298 | } 299 | } 300 | 301 | func TestParseAnsi256(t *testing.T) { 302 | is2 := is.New(t) 303 | tests := []struct { 304 | name string 305 | input string 306 | want []*StyledText 307 | wantErr bool 308 | }{ 309 | {"Grey93 & DarkViolet", "\u001B[38;5;255mGrey93\u001B[0m\u001B[38;5;128mDarkViolet\u001B[0m", []*StyledText{ 310 | {Label: "Grey93", FgCol: &Col{Name: "Grey93"}}, 311 | {Label: "DarkViolet", FgCol: &Col{Name: "DarkViolet"}}, 312 | }, false}, 313 | {"Grey93 Bold & DarkViolet Italic", "\u001B[0;1;38;5;255mGrey93\u001B[0m\u001B[0;3;38;5;128mDarkViolet\u001B[0m", []*StyledText{ 314 | {Label: "Grey93", FgCol: &Col{Name: "Grey93"}, Style: Bold}, 315 | {Label: "DarkViolet", FgCol: &Col{Name: "DarkViolet"}, Style: Italic}, 316 | }, false}, 317 | {"Grey93 Bold & DarkViolet Italic", "\u001B[0;1;38;5;256mGrey93\u001B[0m", nil, true}, 318 | {"Grey93 Bold & DarkViolet Italic", "\u001B[0;1;38;5;-1mGrey93\u001B[0m", nil, true}, 319 | {"Bad No of Params", "\u001B[0;1;38;5mGrey93\u001B[0m", nil, true}, 320 | {"Bad Params", "\u001B[0;1;38;fivemGrey93\u001B[0m", nil, true}, 321 | {"Bad Params 2", "\u001B[0;1;38;3mGrey93\u001B[0m", nil, true}, 322 | {"Bad Params 3", "\u001B[0;1;38;5;fivemGrey93\u001B[0m", nil, true}, 323 | } 324 | for _, tt := range tests { 325 | t.Run(tt.name, func(t *testing.T) { 326 | got, err := Parse(tt.input) 327 | is2.Equal(err != nil, tt.wantErr) 328 | for index, w := range tt.want { 329 | is2.Equal(got[index].Label, w.Label) 330 | if w.FgCol != nil { 331 | is2.Equal(got[index].FgCol.Name, w.FgCol.Name) 332 | } 333 | is2.Equal(got[index].Style, w.Style) 334 | } 335 | }) 336 | } 337 | } 338 | 339 | func TestRoundtripAnsi16(t *testing.T) { 340 | is2 := is.New(t) 341 | tests := []struct { 342 | name string 343 | input string 344 | }{ 345 | {"No formatting", "Hello World"}, 346 | {"Black", "\u001b[0;30mHello World\033[0m"}, 347 | {"Red", "\u001b[0;31mHello World\033[0m"}, 348 | {"Green", "\u001b[0;32mGreen\033[0m"}, 349 | {"Yellow", "\u001b[0;33m😀\033[0m"}, 350 | {"Blue", "\u001b[0;34m123\033[0m"}, 351 | {"Purple", "\u001b[0;35m👩🏽‍🔧\u001B[0m"}, 352 | {"Cyan", "\033[0;36m😀\033[0m"}, 353 | {"White", "\u001b[0;37m[0;37m\033[0m"}, 354 | {"Black Bold", "\u001b[0;1;30mHello World\033[0m"}, 355 | {"Red Bold", "\u001b[0;1;31mHello World\033[0m"}, 356 | {"Green Bold", "\u001b[0;1;32mGreen\033[0m"}, 357 | {"Yellow Bold", "\u001b[0;1;33m😀\033[0m"}, 358 | {"Blue Bold", "\u001b[0;1;34m123\033[0m"}, 359 | {"Purple Bold", "\u001b[0;1;35m👩🏽‍🔧\u001B[0m"}, 360 | {"Cyan Bold", "\033[0;1;36m😀\033[0m"}, 361 | {"White Bold", "\u001b[0;1;37m[0;37m\033[0m"}, 362 | {"Black Bright", "\u001b[0;90mHello World\033[0m"}, 363 | {"Red Bright", "\u001b[0;91mHello World\033[0m"}, 364 | {"Green Bright", "\u001b[0;92mGreen\033[0m"}, 365 | {"Yellow Bright", "\u001b[0;93m😀\033[0m"}, 366 | {"Blue Bright", "\u001b[0;94m123\033[0m"}, 367 | {"Purple Bright", "\u001b[0;95m👩🏽‍🔧\u001B[0m"}, 368 | {"Cyan Bright", "\033[0;96m😀\033[0m"}, 369 | {"White Bright", "\u001b[0;97m[0;37m\033[0m"}, 370 | {"Black Bright Background", "\u001b[0;100mHello World\033[0m"}, 371 | {"Red Bright Background", "\u001b[0;101mHello World\033[0m"}, 372 | {"Green Bright Background", "\u001b[0;102mGreen\033[0m"}, 373 | {"Yellow Bright Background", "\u001b[0;103m😀\033[0m"}, 374 | {"Blue Bright Background", "\u001b[0;104m123\033[0m"}, 375 | {"Purple Bright Background", "\u001b[0;105m👩🏽‍🔧\u001B[0m"}, 376 | {"Cyan Bright Background", "\033[0;106m😀\033[0m"}, 377 | {"White Bright Background", "\u001b[0;107m[0;37m\033[0m"}, 378 | {"Blank", ""}, 379 | {"Emoji", "😀👩🏽‍🔧"}, 380 | {"Spaces", " "}, 381 | } 382 | for _, tt := range tests { 383 | t.Run(tt.name, func(t *testing.T) { 384 | got, err := Parse(tt.input) 385 | is2.NoErr(err) 386 | output := String(got) 387 | is2.Equal(output, tt.input) 388 | }) 389 | } 390 | } 391 | 392 | func TestRoundtripAnsi256(t *testing.T) { 393 | is2 := is.New(t) 394 | tests := []struct { 395 | name string 396 | input string 397 | }{ 398 | {"Grey93 & DarkViolet", "\u001B[0;38;5;255mGrey93\u001B[0m\u001B[0;38;5;128mDarkViolet\u001B[0m"}, 399 | {"Grey93 Bold & DarkViolet Italic", "\u001B[0;1;38;5;255mGrey93\u001B[0m\u001B[0;3;38;5;128mDarkViolet\u001B[0m"}, 400 | {"White", "\u001B[0;38;5;15mWhite\u001B[0m\u001B[0;3;38;5;128mDarkViolet\u001B[0m"}, 401 | {"White Bold", "\u001B[0;1;38;5;15mWhite\u001B[0m\u001B[0;3;38;5;128mDarkViolet\u001B[0m"}, 402 | } 403 | for _, tt := range tests { 404 | t.Run(tt.name, func(t *testing.T) { 405 | got, err := Parse(tt.input) 406 | is2.NoErr(err) 407 | output := String(got) 408 | is2.Equal(output, tt.input) 409 | }) 410 | } 411 | } 412 | 413 | func TestParseAnsiBG256(t *testing.T) { 414 | is2 := is.New(t) 415 | tests := []struct { 416 | name string 417 | input string 418 | want []*StyledText 419 | wantErr bool 420 | }{ 421 | {"Grey93 & DarkViolet", "\u001B[48;5;255mGrey93\u001B[0m\u001B[48;5;128mDarkViolet\u001B[0m", []*StyledText{ 422 | {Label: "Grey93", BgCol: &Col{Name: "Grey93"}}, 423 | {Label: "DarkViolet", BgCol: &Col{Name: "DarkViolet"}}, 424 | }, false}, 425 | {"Grey93 Bold & DarkViolet Italic", "\u001B[0;1;48;5;255mGrey93\u001B[0m\u001B[0;3;48;5;128mDarkViolet\u001B[0m", []*StyledText{ 426 | {Label: "Grey93", BgCol: &Col{Name: "Grey93"}, Style: Bold}, 427 | {Label: "DarkViolet", BgCol: &Col{Name: "DarkViolet"}, Style: Italic}, 428 | }, false}, 429 | {"Grey93 Bold & DarkViolet Italic", "\u001B[0;1;48;5;256mGrey93\u001B[0m", nil, true}, 430 | {"Grey93 Bold & DarkViolet Italic", "\u001B[0;1;48;5;-1mGrey93\u001B[0m", nil, true}, 431 | {"Bad No of Params", "\u001B[0;1;48;5mGrey93\u001B[0m", nil, true}, 432 | {"Bad Params", "\u001B[0;1;48;fivemGrey93\u001B[0m", nil, true}, 433 | {"Bad Params 2", "\u001B[0;1;48;3mGrey93\u001B[0m", nil, true}, 434 | {"Bad Params 2", "\u001B[0;1;50;3mGrey93\u001B[0m", nil, true}, 435 | } 436 | for _, tt := range tests { 437 | t.Run(tt.name, func(t *testing.T) { 438 | got, err := Parse(tt.input) 439 | is2.Equal(err != nil, tt.wantErr) 440 | for index, w := range tt.want { 441 | is2.Equal(got[index].Label, w.Label) 442 | if w.FgCol != nil { 443 | is2.Equal(got[index].BgCol.Name, w.BgCol.Name) 444 | } 445 | is2.Equal(got[index].Style, w.Style) 446 | } 447 | }) 448 | } 449 | } 450 | 451 | func TestParseAnsiTrueColor(t *testing.T) { 452 | is2 := is.New(t) 453 | tests := []struct { 454 | name string 455 | input string 456 | want []*StyledText 457 | wantErr bool 458 | }{ 459 | {"Red", "\u001B[38;2;255;0;0mRed\u001B[0m", []*StyledText{ 460 | {Label: "Red", FgCol: &Col{Rgb: Rgb{255, 0, 0}, Hex: "#ff0000"}}, 461 | }, false}, 462 | {"Red BG", "\u001B[48;2;255;0;0mRed\u001B[0m", []*StyledText{ 463 | {Label: "Red", BgCol: &Col{Rgb: Rgb{255, 0, 0}, Hex: "#ff0000"}}, 464 | }, false}, 465 | {"Red, text, Green", "\u001B[38;2;255;0;0mRed\u001B[0mI am plain text\u001B[38;2;0;255;0mGreen\u001B[0m", []*StyledText{ 466 | {Label: "Red", FgCol: &Col{Rgb: Rgb{255, 0, 0}, Hex: "#ff0000"}}, 467 | {Label: "I am plain text"}, 468 | {Label: "Green", FgCol: &Col{Rgb: Rgb{0, 255, 0}, Hex: "#00ff00"}}, 469 | }, false}, 470 | {"Bad 1", "\u001B[38;2;256;0;0mRed\u001B[0m", nil, true}, 471 | {"Bad 2", "\u001B[38;2;-1;0;0mRed\u001B[0m", nil, true}, 472 | {"Bad no of params", "\u001B[38;2;0;0mRed\u001B[0m", nil, true}, 473 | {"Bad params", "\u001B[38;2;0;onemRed\u001B[0m", nil, true}, 474 | {"Bad params 2", "\u001B[38;2;red;0;0mRed\u001B[0m", nil, true}, 475 | {"Bad params 3", "\u001B[38;2;0;red;0mRed\u001B[0m", nil, true}, 476 | {"Bad params 4", "\u001B[38;2;0;0;redmRed\u001B[0m", nil, true}, 477 | {"Bad params 4", "\u001B[38;3;0;0;redmRed\u001B[0m", nil, true}, 478 | } 479 | for _, tt := range tests { 480 | t.Run(tt.name, func(t *testing.T) { 481 | got, err := Parse(tt.input) 482 | is2.Equal(err != nil, tt.wantErr) 483 | for index, w := range tt.want { 484 | is2.Equal(got[index].Label, w.Label) 485 | if w.FgCol != nil { 486 | is2.Equal(got[index].FgCol.Hex, w.FgCol.Hex) 487 | is2.Equal(got[index].FgCol.Rgb, w.FgCol.Rgb) 488 | } 489 | if w.BgCol != nil { 490 | is2.Equal(got[index].BgCol.Hex, w.BgCol.Hex) 491 | is2.Equal(got[index].BgCol.Rgb, w.BgCol.Rgb) 492 | } 493 | is2.Equal(got[index].Style, w.Style) 494 | } 495 | }) 496 | } 497 | } 498 | 499 | func TestParseAnsiWithOptions(t *testing.T) { 500 | is2 := is.New(t) 501 | tests := []struct { 502 | name string 503 | input string 504 | options []ParseOption 505 | want []*StyledText 506 | wantErr bool 507 | }{ 508 | { 509 | "Unexpected code errored", "\u001b[0;99mHello World\033[0m", 510 | nil, nil, true, 511 | }, 512 | { 513 | "Unexpected code ignored", "\u001b[0;99mHello World\033[0m", 514 | []ParseOption{WithIgnoreInvalidCodes()}, 515 | []*StyledText{{Label: "Hello World"}}, false, 516 | }, 517 | { 518 | "Foreground code default", "\u001b[0;39mHello World\033[0m", nil, 519 | []*StyledText{{Label: "Hello World", FgCol: Cols[7]}}, false, 520 | }, 521 | { 522 | "Foreground code specified", "\u001b[0;39mHello World\033[0m", 523 | []ParseOption{WithDefaultForegroundColor("35")}, 524 | []*StyledText{{Label: "Hello World", FgCol: Cols[5]}}, false, 525 | }, 526 | { 527 | "Background code default", "\u001b[0;49mHello World\033[0m", nil, 528 | []*StyledText{{Label: "Hello World", BgCol: Cols[0]}}, false, 529 | }, 530 | { 531 | "Background code specified", "\u001b[0;49mHello World\033[0m", 532 | []ParseOption{WithDefaultBackgroundColor("36")}, 533 | []*StyledText{{Label: "Hello World", BgCol: Cols[6]}}, false, 534 | }, 535 | } 536 | for _, tt := range tests { 537 | t.Run(tt.name, func(t *testing.T) { 538 | got, err := Parse(tt.input, tt.options...) 539 | is2.Equal(err != nil, tt.wantErr) 540 | for index, w := range tt.want { 541 | is2.Equal(got[index].Label, w.Label) 542 | if w.FgCol != nil { 543 | is2.Equal(got[index].FgCol.Name, w.FgCol.Name) 544 | } 545 | if w.BgCol != nil { 546 | is2.Equal(got[index].BgCol.Name, w.BgCol.Name) 547 | } 548 | is2.Equal(got[index].Style, w.Style) 549 | } 550 | }) 551 | } 552 | } 553 | 554 | func TestHasEscapeCodes(t *testing.T) { 555 | is2 := is.New(t) 556 | tests := []struct { 557 | name string 558 | input string 559 | want bool 560 | }{ 561 | {"yes", "\u001B[0;30mHello World\033[0m\u001B[0;31mHello World\u001B[0m", true}, 562 | {"no", "This is great!", false}, 563 | } 564 | for _, tt := range tests { 565 | t.Run(tt.name, func(t *testing.T) { 566 | got := HasEscapeCodes(tt.input) 567 | is2.Equal(got, tt.want) 568 | }) 569 | } 570 | } 571 | 572 | func TestString(t *testing.T) { 573 | is2 := is.New(t) 574 | tests := []struct { 575 | name string 576 | input []*StyledText 577 | want string 578 | }{ 579 | {"Blank", []*StyledText{}, ""}, 580 | {"ANSI16 Fg", []*StyledText{{Label: "Red", FgCol: Cols[1]}}, "\033[0;31mRed\033[0m"}, 581 | {"ANSI16 Fg Bold", []*StyledText{{Label: "Red", FgCol: Cols[1], Style: Bold}}, "\033[0;1;31mRed\033[0m"}, 582 | {"ANSI16 Fg Strikethrough", []*StyledText{{Label: "Red", FgCol: Cols[1], Style: Strikethrough}}, "\033[0;9;31mRed\033[0m"}, 583 | {"ANSI16 Fg Bold & Italic", []*StyledText{{Label: "Red", FgCol: Cols[1], Style: Bold | Italic}}, "\033[0;1;3;31mRed\033[0m"}, 584 | {"ANSI16 Bg", []*StyledText{{Label: "Black", BgCol: Cols[0]}}, "\033[0;40mBlack\033[0m"}, 585 | {"ANSI16 Mixed", []*StyledText{{Label: "Mixed", FgCol: Cols[1], BgCol: Cols[0]}}, "\033[0;31;40mMixed\033[0m"}, 586 | {"ANSI256 Fg", []*StyledText{{ColourMode: TwoFiveSix, Label: "Dark Blue", FgCol: Cols[18]}}, "\033[0;38;5;18mDark Blue\033[0m"}, 587 | {"ANSI256 Fg Bold", []*StyledText{{ColourMode: TwoFiveSix, Label: "Dark Blue", FgCol: Cols[18], Style: Bold}}, "\033[0;1;38;5;18mDark Blue\033[0m"}, 588 | {"ANSI256 Bg", []*StyledText{{ColourMode: TwoFiveSix, Label: "Dark Blue", BgCol: Cols[18]}}, "\033[0;48;5;18mDark Blue\033[0m"}, 589 | {"ANSI256 Bg Bold", []*StyledText{{ColourMode: TwoFiveSix, Label: "Dark Blue", BgCol: Cols[18], Style: Bold}}, "\033[0;1;48;5;18mDark Blue\033[0m"}, 590 | {"Truecolor Fg", []*StyledText{{ColourMode: TrueColour, Label: "TrueColor!", FgCol: &Col{Id: 256, Rgb: Rgb{R: 128, G: 127, B: 126}}}}, "\033[0;38;2;128;127;126mTrueColor!\033[0m"}, 591 | {"Truecolor Fg Bold", []*StyledText{{ColourMode: TrueColour, Label: "TrueColor!", FgCol: &Col{Id: 256, Rgb: Rgb{R: 128, G: 127, B: 126}}, Style: Bold}}, "\033[0;1;38;2;128;127;126mTrueColor!\033[0m"}, 592 | {"Truecolor Bg", []*StyledText{{ColourMode: TrueColour, Label: "TrueColor!", BgCol: &Col{Id: 256, Rgb: Rgb{R: 128, G: 127, B: 126}}}}, "\033[0;48;2;128;127;126mTrueColor!\033[0m"}, 593 | {"Truecolor Bg Bold", []*StyledText{{ColourMode: TrueColour, Label: "TrueColor!", BgCol: &Col{Id: 256, Rgb: Rgb{R: 128, G: 127, B: 126}}, Style: Bold}}, "\033[0;1;48;2;128;127;126mTrueColor!\033[0m"}, 594 | {"Truecolor Mixed", []*StyledText{{ColourMode: TrueColour, Label: "TrueColor!", FgCol: &Col{Id: 256, Rgb: Rgb{R: 90, G: 91, B: 92}}, BgCol: &Col{Id: 256, Rgb: Rgb{R: 128, G: 127, B: 126}}, Style: Bold | Faint | Underlined | Strikethrough | Italic | Invisible | Blinking | Inversed}}, "\033[0;1;2;3;4;5;7;8;9;38;2;90;91;92;48;2;128;127;126mTrueColor!\033[0m"}, 595 | } 596 | for _, tt := range tests { 597 | t.Run(tt.name, func(t *testing.T) { 598 | got := String(tt.input) 599 | is2.Equal(got, tt.want) 600 | }) 601 | } 602 | } 603 | 604 | func TestTruncate(t *testing.T) { 605 | is2 := is.New(t) 606 | tests := []struct { 607 | name string 608 | input string 609 | maxChars int 610 | want string 611 | wantErr bool 612 | }{ 613 | {"No formatting", "Hello World", 11, "Hello World", false}, 614 | {"No formatting truncated", "Hello World", 5, "Hello", false}, 615 | {"No formatting many chars", "Hello World", 50, "Hello World", false}, 616 | {"Black", "\u001b[0;30mHello World\033[0m", 5, "\u001B[0;30mHello\u001B[0m", false}, 617 | {"Red Bold", "\u001b[0;1;31mHello World\033[0m", 5, "\033[0;1;31mHello\033[0m", false}, 618 | {"Red Bold & Black", "\u001b[0;1;31mI am Red\033[0m\u001B[0;30mI am Black\u001B[0m", 12, "\u001B[0;1;31mI am Red\u001B[0m\u001B[0;30mI am\u001B[0m", false}, 619 | {"Red Bold & text & Black", "\u001b[0;1;31mI am Red\033[0m and \u001B[0;30mI am Black\u001B[0m", 17, "\u001B[0;1;31mI am Red\u001B[0m and \u001B[0;30mI am\u001B[0m", false}, 620 | {"Emoji", "\u001B[0;1;31m😀👩🏽‍🔧\u001B[0m", 1, "\u001B[0;1;31m😀\u001B[0m", false}, 621 | {"Emoji 2", "\u001B[0;1;31m😀👩🏽‍🔧\u001B[0m", 2, "\u001B[0;1;31m😀👩🏽‍🔧\u001B[0m", false}, 622 | {"Emoji 3", "\u001B[0;1;31m😀👩🏽‍🔧😀\u001B[0m", 2, "\u001B[0;1;31m😀👩🏽‍🔧\u001B[0m", false}, 623 | {"Bad", "\033[44;32;12", 10, "", true}, 624 | 625 | //{"Spaces", " ", " ", "", false}, 626 | //{"Bad code", "\u001b[1 ", "", "", true}, 627 | } 628 | for _, tt := range tests { 629 | t.Run(tt.name, func(t *testing.T) { 630 | got, err := Truncate(tt.input, tt.maxChars) 631 | is2.Equal(err != nil, tt.wantErr) 632 | is2.Equal(got, tt.want) 633 | }) 634 | } 635 | } 636 | 637 | func TestCleanse(t *testing.T) { 638 | is2 := is.New(t) 639 | tests := []struct { 640 | name string 641 | input string 642 | want string 643 | wantErr bool 644 | }{ 645 | {"Blank", "", "", false}, 646 | {"No formatting", "Hello World", "Hello World", false}, 647 | {"ANSI16 Fg", "\033[0;31mRed\033[0m", "Red", false}, 648 | {"Black", "\u001b[0;30mHello World\033[0m", "Hello World", false}, 649 | {"Red Bold & Black", "\u001b[0;1;31mI am Red\033[0m & \u001B[0;30mI am Black\u001B[0m", "I am Red & I am Black", false}, 650 | {"Red All the styles", "\u001b[0;1;2;3;4;5;7;8;9;31mI am Red\033[0m", "I am Red", false}, 651 | {"Emoji", "\u001B[0;1;31m😀\u001B[0m", "😀", false}, 652 | {"Emoji 2", "\u001B[0;1;31m😀👩🏽‍🔧\u001B[0m", "😀👩🏽‍🔧", false}, 653 | {"Bad", "\033[44;32;12", "", true}, 654 | } 655 | for _, tt := range tests { 656 | t.Run(tt.name, func(t *testing.T) { 657 | got, err := Cleanse(tt.input) 658 | is2.Equal(err != nil, tt.wantErr) 659 | is2.Equal(got, tt.want) 660 | }) 661 | } 662 | } 663 | 664 | func TestLength(t *testing.T) { 665 | is2 := is.New(t) 666 | tests := []struct { 667 | name string 668 | input string 669 | want int 670 | wantErr bool 671 | }{ 672 | {"Blank", "", 0, false}, 673 | {"No formatting", "Hello World", 11, false}, 674 | {"ANSI16 Fg", "\033[0;31mRed\033[0m", 3, false}, 675 | {"Zero chats", "\u001b[0;30m\033[0m", 0, false}, 676 | {"Red Bold & Black", "\u001b[0;1;31mI am Red\033[0m & \u001B[0;30mI am Black\u001B[0m", 21, false}, 677 | {"Red All the styles", "\u001b[0;1;2;3;4;5;7;8;9;31mI am Red\033[0m", 8, false}, 678 | {"Emoji", "\u001B[0;1;31m😀\u001B[0m", 1, false}, 679 | {"Emoji 2", "\u001B[0;1;31m😀👩🏽‍🔧\u001B[0m", 2, false}, 680 | {"Bad", "\033[44;32;12", -1, true}, 681 | } 682 | for _, tt := range tests { 683 | t.Run(tt.name, func(t *testing.T) { 684 | got, err := Length(tt.input) 685 | is2.Equal(err != nil, tt.wantErr) 686 | is2.Equal(got, tt.want) 687 | }) 688 | } 689 | } 690 | 691 | func TestStripLeadingZeros(t *testing.T) { 692 | is2 := is.New(t) 693 | tests := []struct { 694 | name string 695 | input string 696 | want string 697 | }{ 698 | {"Blank", "", ""}, 699 | {"One rune not 0", "4", "4"}, 700 | {"Only 0", "0", "0"}, 701 | {"Multi-digit non-0", "35", "35"}, 702 | {"Two-digit leading 0", "05", "5"}, 703 | {"Three-digit leading 0", "045", "45"}, 704 | } 705 | for _, tt := range tests { 706 | t.Run(tt.name, func(t *testing.T) { 707 | got := stripLeadingZeros(tt.input) 708 | is2.Equal(got, tt.want) 709 | }) 710 | } 711 | } 712 | -------------------------------------------------------------------------------- /cols.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | // Rgb represents an RGB colour value 4 | // with 8bits per channel 5 | type Rgb struct { 6 | R uint8 `json:"r"` 7 | G uint8 `json:"g"` 8 | B uint8 `json:"b"` 9 | } 10 | 11 | // Hsl represents the HSL value of the colour 12 | type Hsl struct { 13 | H float64 `json:"h"` 14 | S float64 `json:"s"` 15 | L float64 `json:"l"` 16 | } 17 | 18 | // Col represents a colour value. 19 | // The Id is the ANSI colour ID. 20 | type Col struct { 21 | Id int `json:"id"` 22 | Hex string `json:"hex"` 23 | Rgb Rgb `json:"rgb"` 24 | Hsl Hsl `json:"hsl"` 25 | Name string `json:"name"` 26 | } 27 | 28 | // Cols represents the default colour definitions 29 | // used by the library. This may be overridden. 30 | var Cols = []*Col{ 31 | { 32 | Id: 0, 33 | Hex: "#000000", 34 | Rgb: Rgb{0, 0, 0}, 35 | Hsl: Hsl{0, 0, 0}, 36 | Name: "Black", 37 | }, 38 | { 39 | Id: 1, 40 | Hex: "#800000", 41 | Rgb: Rgb{128, 0, 0}, 42 | Hsl: Hsl{0, 100, 25}, 43 | Name: "Maroon", 44 | }, 45 | { 46 | Id: 2, 47 | Hex: "#008000", 48 | Rgb: Rgb{0, 128, 0}, 49 | Hsl: Hsl{120, 100, 25}, 50 | Name: "Green", 51 | }, 52 | { 53 | Id: 3, 54 | Hex: "#808000", 55 | Rgb: Rgb{128, 128, 0}, 56 | Hsl: Hsl{60, 100, 25}, 57 | Name: "Olive", 58 | }, 59 | { 60 | Id: 4, 61 | Hex: "#000080", 62 | Rgb: Rgb{0, 0, 128}, 63 | Hsl: Hsl{240, 100, 25}, 64 | Name: "Navy", 65 | }, 66 | { 67 | Id: 5, 68 | Hex: "#800080", 69 | Rgb: Rgb{128, 0, 128}, 70 | Hsl: Hsl{300, 100, 25}, 71 | Name: "Purple", 72 | }, 73 | { 74 | Id: 6, 75 | Hex: "#008080", 76 | Rgb: Rgb{0, 128, 128}, 77 | Hsl: Hsl{180, 100, 25}, 78 | Name: "Teal", 79 | }, 80 | { 81 | Id: 7, 82 | Hex: "#c0c0c0", 83 | Rgb: Rgb{192, 192, 192}, 84 | Hsl: Hsl{0, 0, 75}, 85 | Name: "Silver", 86 | }, 87 | { 88 | Id: 8, 89 | Hex: "#808080", 90 | Rgb: Rgb{128, 128, 128}, 91 | Hsl: Hsl{0, 0, 50}, 92 | Name: "Grey", 93 | }, 94 | { 95 | Id: 9, 96 | Hex: "#ff0000", 97 | Rgb: Rgb{255, 0, 0}, 98 | Hsl: Hsl{0, 100, 50}, 99 | Name: "Red", 100 | }, 101 | { 102 | Id: 10, 103 | Hex: "#00ff00", 104 | Rgb: Rgb{0, 255, 0}, 105 | Hsl: Hsl{120, 100, 50}, 106 | Name: "Lime", 107 | }, 108 | { 109 | Id: 11, 110 | Hex: "#ffff00", 111 | Rgb: Rgb{255, 255, 0}, 112 | Hsl: Hsl{60, 100, 50}, 113 | Name: "Yellow", 114 | }, 115 | { 116 | Id: 12, 117 | Hex: "#0000ff", 118 | Rgb: Rgb{0, 0, 255}, 119 | Hsl: Hsl{240, 100, 50}, 120 | Name: "Blue", 121 | }, 122 | { 123 | Id: 13, 124 | Hex: "#ff00ff", 125 | Rgb: Rgb{255, 0, 255}, 126 | Hsl: Hsl{300, 100, 50}, 127 | Name: "Fuchsia", 128 | }, 129 | { 130 | Id: 14, 131 | Hex: "#00ffff", 132 | Rgb: Rgb{0, 255, 255}, 133 | Hsl: Hsl{180, 100, 50}, 134 | Name: "Aqua", 135 | }, 136 | { 137 | Id: 15, 138 | Hex: "#ffffff", 139 | Rgb: Rgb{255, 255, 255}, 140 | Hsl: Hsl{0, 0, 100}, 141 | Name: "White", 142 | }, 143 | { 144 | Id: 16, 145 | Hex: "#000000", 146 | Rgb: Rgb{0, 0, 0}, 147 | Hsl: Hsl{0, 0, 0}, 148 | Name: "Grey0", 149 | }, 150 | { 151 | Id: 17, 152 | Hex: "#00005f", 153 | Rgb: Rgb{0, 0, 95}, 154 | Hsl: Hsl{240, 100, 18}, 155 | Name: "NavyBlue", 156 | }, 157 | { 158 | Id: 18, 159 | Hex: "#000087", 160 | Rgb: Rgb{0, 0, 135}, 161 | Hsl: Hsl{240, 100, 26}, 162 | Name: "DarkBlue", 163 | }, 164 | { 165 | Id: 19, 166 | Hex: "#0000af", 167 | Rgb: Rgb{0, 0, 175}, 168 | Hsl: Hsl{240, 100, 34}, 169 | Name: "Blue3", 170 | }, 171 | { 172 | Id: 20, 173 | Hex: "#0000d7", 174 | Rgb: Rgb{0, 0, 215}, 175 | Hsl: Hsl{240, 100, 42}, 176 | Name: "Blue3", 177 | }, 178 | { 179 | Id: 21, 180 | Hex: "#0000ff", 181 | Rgb: Rgb{0, 0, 255}, 182 | Hsl: Hsl{240, 100, 50}, 183 | Name: "Blue1", 184 | }, 185 | { 186 | Id: 22, 187 | Hex: "#005f00", 188 | Rgb: Rgb{0, 95, 0}, 189 | Hsl: Hsl{120, 100, 18}, 190 | Name: "DarkGreen", 191 | }, 192 | { 193 | Id: 23, 194 | Hex: "#005f5f", 195 | Rgb: Rgb{0, 95, 95}, 196 | Hsl: Hsl{180, 100, 18}, 197 | Name: "DeepSkyBlue4", 198 | }, 199 | { 200 | Id: 24, 201 | Hex: "#005f87", 202 | Rgb: Rgb{0, 95, 135}, 203 | Hsl: Hsl{197.777777777778, 100, 26}, 204 | Name: "DeepSkyBlue4", 205 | }, 206 | { 207 | Id: 25, 208 | Hex: "#005faf", 209 | Rgb: Rgb{0, 95, 175}, 210 | Hsl: Hsl{207.428571428571, 100, 34}, 211 | Name: "DeepSkyBlue4", 212 | }, 213 | { 214 | Id: 26, 215 | Hex: "#005fd7", 216 | Rgb: Rgb{0, 95, 215}, 217 | Hsl: Hsl{213.488372093023, 100, 42}, 218 | Name: "DodgerBlue3", 219 | }, 220 | { 221 | Id: 27, 222 | Hex: "#005fff", 223 | Rgb: Rgb{0, 95, 255}, 224 | Hsl: Hsl{217.647058823529, 100, 50}, 225 | Name: "DodgerBlue2", 226 | }, 227 | { 228 | Id: 28, 229 | Hex: "#008700", 230 | Rgb: Rgb{0, 135, 0}, 231 | Hsl: Hsl{120, 100, 26}, 232 | Name: "Green4", 233 | }, 234 | { 235 | Id: 29, 236 | Hex: "#00875f", 237 | Rgb: Rgb{0, 135, 95}, 238 | Hsl: Hsl{162.222222222222, 100, 26}, 239 | Name: "SpringGreen4", 240 | }, 241 | { 242 | Id: 30, 243 | Hex: "#008787", 244 | Rgb: Rgb{0, 135, 135}, 245 | Hsl: Hsl{180, 100, 26}, 246 | Name: "Turquoise4", 247 | }, 248 | { 249 | Id: 31, 250 | Hex: "#0087af", 251 | Rgb: Rgb{0, 135, 175}, 252 | Hsl: Hsl{193.714285714286, 100, 34}, 253 | Name: "DeepSkyBlue3", 254 | }, 255 | { 256 | Id: 32, 257 | Hex: "#0087d7", 258 | Rgb: Rgb{0, 135, 215}, 259 | Hsl: Hsl{202.325581395349, 100, 42}, 260 | Name: "DeepSkyBlue3", 261 | }, 262 | { 263 | Id: 33, 264 | Hex: "#0087ff", 265 | Rgb: Rgb{0, 135, 255}, 266 | Hsl: Hsl{208.235294117647, 100, 50}, 267 | Name: "DodgerBlue1", 268 | }, 269 | { 270 | Id: 34, 271 | Hex: "#00af00", 272 | Rgb: Rgb{0, 175, 0}, 273 | Hsl: Hsl{120, 100, 34}, 274 | Name: "Green3", 275 | }, 276 | { 277 | Id: 35, 278 | Hex: "#00af5f", 279 | Rgb: Rgb{0, 175, 95}, 280 | Hsl: Hsl{152.571428571429, 100, 34}, 281 | Name: "SpringGreen3", 282 | }, 283 | { 284 | Id: 36, 285 | Hex: "#00af87", 286 | Rgb: Rgb{0, 175, 135}, 287 | Hsl: Hsl{166.285714285714, 100, 34}, 288 | Name: "DarkCyan", 289 | }, 290 | { 291 | Id: 37, 292 | Hex: "#00afaf", 293 | Rgb: Rgb{0, 175, 175}, 294 | Hsl: Hsl{180, 100, 34}, 295 | Name: "LightSeaGreen", 296 | }, 297 | { 298 | Id: 38, 299 | Hex: "#00afd7", 300 | Rgb: Rgb{0, 175, 215}, 301 | Hsl: Hsl{191.162790697674, 100, 42}, 302 | Name: "DeepSkyBlue2", 303 | }, 304 | { 305 | Id: 39, 306 | Hex: "#00afff", 307 | Rgb: Rgb{0, 175, 255}, 308 | Hsl: Hsl{198.823529411765, 100, 50}, 309 | Name: "DeepSkyBlue1", 310 | }, 311 | { 312 | Id: 40, 313 | Hex: "#00d700", 314 | Rgb: Rgb{0, 215, 0}, 315 | Hsl: Hsl{120, 100, 42}, 316 | Name: "Green3", 317 | }, 318 | { 319 | Id: 41, 320 | Hex: "#00d75f", 321 | Rgb: Rgb{0, 215, 95}, 322 | Hsl: Hsl{146.511627906977, 100, 42}, 323 | Name: "SpringGreen3", 324 | }, 325 | { 326 | Id: 42, 327 | Hex: "#00d787", 328 | Rgb: Rgb{0, 215, 135}, 329 | Hsl: Hsl{157.674418604651, 100, 42}, 330 | Name: "SpringGreen2", 331 | }, 332 | { 333 | Id: 43, 334 | Hex: "#00d7af", 335 | Rgb: Rgb{0, 215, 175}, 336 | Hsl: Hsl{168.837209302326, 100, 42}, 337 | Name: "Cyan3", 338 | }, 339 | { 340 | Id: 44, 341 | Hex: "#00d7d7", 342 | Rgb: Rgb{0, 215, 215}, 343 | Hsl: Hsl{180, 100, 42}, 344 | Name: "DarkTurquoise", 345 | }, 346 | { 347 | Id: 45, 348 | Hex: "#00d7ff", 349 | Rgb: Rgb{0, 215, 255}, 350 | Hsl: Hsl{189.411764705882, 100, 50}, 351 | Name: "Turquoise2", 352 | }, 353 | { 354 | Id: 46, 355 | Hex: "#00ff00", 356 | Rgb: Rgb{0, 255, 0}, 357 | Hsl: Hsl{120, 100, 50}, 358 | Name: "Green1", 359 | }, 360 | { 361 | Id: 47, 362 | Hex: "#00ff5f", 363 | Rgb: Rgb{0, 255, 95}, 364 | Hsl: Hsl{142.352941176471, 100, 50}, 365 | Name: "SpringGreen2", 366 | }, 367 | { 368 | Id: 48, 369 | Hex: "#00ff87", 370 | Rgb: Rgb{0, 255, 135}, 371 | Hsl: Hsl{151.764705882353, 100, 50}, 372 | Name: "SpringGreen1", 373 | }, 374 | { 375 | Id: 49, 376 | Hex: "#00ffaf", 377 | Rgb: Rgb{0, 255, 175}, 378 | Hsl: Hsl{161.176470588235, 100, 50}, 379 | Name: "MediumSpringGreen", 380 | }, 381 | { 382 | Id: 50, 383 | Hex: "#00ffd7", 384 | Rgb: Rgb{0, 255, 215}, 385 | Hsl: Hsl{170.588235294118, 100, 50}, 386 | Name: "Cyan2", 387 | }, 388 | { 389 | Id: 51, 390 | Hex: "#00ffff", 391 | Rgb: Rgb{0, 255, 255}, 392 | Hsl: Hsl{180, 100, 50}, 393 | Name: "Cyan1", 394 | }, 395 | { 396 | Id: 52, 397 | Hex: "#5f0000", 398 | Rgb: Rgb{95, 0, 0}, 399 | Hsl: Hsl{0, 100, 18}, 400 | Name: "DarkRed", 401 | }, 402 | { 403 | Id: 53, 404 | Hex: "#5f005f", 405 | Rgb: Rgb{95, 0, 95}, 406 | Hsl: Hsl{300, 100, 18}, 407 | Name: "DeepPink4", 408 | }, 409 | { 410 | Id: 54, 411 | Hex: "#5f0087", 412 | Rgb: Rgb{95, 0, 135}, 413 | Hsl: Hsl{282.222222222222, 100, 26}, 414 | Name: "Purple4", 415 | }, 416 | { 417 | Id: 55, 418 | Hex: "#5f00af", 419 | Rgb: Rgb{95, 0, 175}, 420 | Hsl: Hsl{272.571428571429, 100, 34}, 421 | Name: "Purple4", 422 | }, 423 | { 424 | Id: 56, 425 | Hex: "#5f00d7", 426 | Rgb: Rgb{95, 0, 215}, 427 | Hsl: Hsl{266.511627906977, 100, 42}, 428 | Name: "Purple3", 429 | }, 430 | { 431 | Id: 57, 432 | Hex: "#5f00ff", 433 | Rgb: Rgb{95, 0, 255}, 434 | Hsl: Hsl{262.352941176471, 100, 50}, 435 | Name: "BlueViolet", 436 | }, 437 | { 438 | Id: 58, 439 | Hex: "#5f5f00", 440 | Rgb: Rgb{95, 95, 0}, 441 | Hsl: Hsl{60, 100, 18}, 442 | Name: "Orange4", 443 | }, 444 | { 445 | Id: 59, 446 | Hex: "#5f5f5f", 447 | Rgb: Rgb{95, 95, 95}, 448 | Hsl: Hsl{0, 0, 37}, 449 | Name: "Grey37", 450 | }, 451 | { 452 | Id: 60, 453 | Hex: "#5f5f87", 454 | Rgb: Rgb{95, 95, 135}, 455 | Hsl: Hsl{240, 17, 45}, 456 | Name: "MediumPurple4", 457 | }, 458 | { 459 | Id: 61, 460 | Hex: "#5f5faf", 461 | Rgb: Rgb{95, 95, 175}, 462 | Hsl: Hsl{240, 33, 52}, 463 | Name: "SlateBlue3", 464 | }, 465 | { 466 | Id: 62, 467 | Hex: "#5f5fd7", 468 | Rgb: Rgb{95, 95, 215}, 469 | Hsl: Hsl{240, 60, 60}, 470 | Name: "SlateBlue3", 471 | }, 472 | { 473 | Id: 63, 474 | Hex: "#5f5fff", 475 | Rgb: Rgb{95, 95, 255}, 476 | Hsl: Hsl{240, 100, 68}, 477 | Name: "RoyalBlue1", 478 | }, 479 | { 480 | Id: 64, 481 | Hex: "#5f8700", 482 | Rgb: Rgb{95, 135, 0}, 483 | Hsl: Hsl{77.7777777777778, 100, 26}, 484 | Name: "Chartreuse4", 485 | }, 486 | { 487 | Id: 65, 488 | Hex: "#5f875f", 489 | Rgb: Rgb{95, 135, 95}, 490 | Hsl: Hsl{120, 17, 45}, 491 | Name: "DarkSeaGreen4", 492 | }, 493 | { 494 | Id: 66, 495 | Hex: "#5f8787", 496 | Rgb: Rgb{95, 135, 135}, 497 | Hsl: Hsl{180, 17, 45}, 498 | Name: "PaleTurquoise4", 499 | }, 500 | { 501 | Id: 67, 502 | Hex: "#5f87af", 503 | Rgb: Rgb{95, 135, 175}, 504 | Hsl: Hsl{210, 33, 52}, 505 | Name: "SteelBlue", 506 | }, 507 | { 508 | Id: 68, 509 | Hex: "#5f87d7", 510 | Rgb: Rgb{95, 135, 215}, 511 | Hsl: Hsl{220, 60, 60}, 512 | Name: "SteelBlue3", 513 | }, 514 | { 515 | Id: 69, 516 | Hex: "#5f87ff", 517 | Rgb: Rgb{95, 135, 255}, 518 | Hsl: Hsl{225, 100, 68}, 519 | Name: "CornflowerBlue", 520 | }, 521 | { 522 | Id: 70, 523 | Hex: "#5faf00", 524 | Rgb: Rgb{95, 175, 0}, 525 | Hsl: Hsl{87.4285714285714, 100, 34}, 526 | Name: "Chartreuse3", 527 | }, 528 | { 529 | Id: 71, 530 | Hex: "#5faf5f", 531 | Rgb: Rgb{95, 175, 95}, 532 | Hsl: Hsl{120, 33, 52}, 533 | Name: "DarkSeaGreen4", 534 | }, 535 | { 536 | Id: 72, 537 | Hex: "#5faf87", 538 | Rgb: Rgb{95, 175, 135}, 539 | Hsl: Hsl{150, 33, 52}, 540 | Name: "CadetBlue", 541 | }, 542 | { 543 | Id: 73, 544 | Hex: "#5fafaf", 545 | Rgb: Rgb{95, 175, 175}, 546 | Hsl: Hsl{180, 33, 52}, 547 | Name: "CadetBlue", 548 | }, 549 | { 550 | Id: 74, 551 | Hex: "#5fafd7", 552 | Rgb: Rgb{95, 175, 215}, 553 | Hsl: Hsl{200, 60, 60}, 554 | Name: "SkyBlue3", 555 | }, 556 | { 557 | Id: 75, 558 | Hex: "#5fafff", 559 | Rgb: Rgb{95, 175, 255}, 560 | Hsl: Hsl{210, 100, 68}, 561 | Name: "SteelBlue1", 562 | }, 563 | { 564 | Id: 76, 565 | Hex: "#5fd700", 566 | Rgb: Rgb{95, 215, 0}, 567 | Hsl: Hsl{93.4883720930233, 100, 42}, 568 | Name: "Chartreuse3", 569 | }, 570 | { 571 | Id: 77, 572 | Hex: "#5fd75f", 573 | Rgb: Rgb{95, 215, 95}, 574 | Hsl: Hsl{120, 60, 60}, 575 | Name: "PaleGreen3", 576 | }, 577 | { 578 | Id: 78, 579 | Hex: "#5fd787", 580 | Rgb: Rgb{95, 215, 135}, 581 | Hsl: Hsl{140, 60, 60}, 582 | Name: "SeaGreen3", 583 | }, 584 | { 585 | Id: 79, 586 | Hex: "#5fd7af", 587 | Rgb: Rgb{95, 215, 175}, 588 | Hsl: Hsl{160, 60, 60}, 589 | Name: "Aquamarine3", 590 | }, 591 | { 592 | Id: 80, 593 | Hex: "#5fd7d7", 594 | Rgb: Rgb{95, 215, 215}, 595 | Hsl: Hsl{180, 60, 60}, 596 | Name: "MediumTurquoise", 597 | }, 598 | { 599 | Id: 81, 600 | Hex: "#5fd7ff", 601 | Rgb: Rgb{95, 215, 255}, 602 | Hsl: Hsl{195, 100, 68}, 603 | Name: "SteelBlue1", 604 | }, 605 | { 606 | Id: 82, 607 | Hex: "#5fff00", 608 | Rgb: Rgb{95, 255, 0}, 609 | Hsl: Hsl{97.6470588235294, 100, 50}, 610 | Name: "Chartreuse2", 611 | }, 612 | { 613 | Id: 83, 614 | Hex: "#5fff5f", 615 | Rgb: Rgb{95, 255, 95}, 616 | Hsl: Hsl{120, 100, 68}, 617 | Name: "SeaGreen2", 618 | }, 619 | { 620 | Id: 84, 621 | Hex: "#5fff87", 622 | Rgb: Rgb{95, 255, 135}, 623 | Hsl: Hsl{135, 100, 68}, 624 | Name: "SeaGreen1", 625 | }, 626 | { 627 | Id: 85, 628 | Hex: "#5fffaf", 629 | Rgb: Rgb{95, 255, 175}, 630 | Hsl: Hsl{150, 100, 68}, 631 | Name: "SeaGreen1", 632 | }, 633 | { 634 | Id: 86, 635 | Hex: "#5fffd7", 636 | Rgb: Rgb{95, 255, 215}, 637 | Hsl: Hsl{165, 100, 68}, 638 | Name: "Aquamarine1", 639 | }, 640 | { 641 | Id: 87, 642 | Hex: "#5fffff", 643 | Rgb: Rgb{95, 255, 255}, 644 | Hsl: Hsl{180, 100, 68}, 645 | Name: "DarkSlateGray2", 646 | }, 647 | { 648 | Id: 88, 649 | Hex: "#870000", 650 | Rgb: Rgb{135, 0, 0}, 651 | Hsl: Hsl{0, 100, 26}, 652 | Name: "DarkRed", 653 | }, 654 | { 655 | Id: 89, 656 | Hex: "#87005f", 657 | Rgb: Rgb{135, 0, 95}, 658 | Hsl: Hsl{317.777777777778, 100, 26}, 659 | Name: "DeepPink4", 660 | }, 661 | { 662 | Id: 90, 663 | Hex: "#870087", 664 | Rgb: Rgb{135, 0, 135}, 665 | Hsl: Hsl{300, 100, 26}, 666 | Name: "DarkMagenta", 667 | }, 668 | { 669 | Id: 91, 670 | Hex: "#8700af", 671 | Rgb: Rgb{135, 0, 175}, 672 | Hsl: Hsl{286.285714285714, 100, 34}, 673 | Name: "DarkMagenta", 674 | }, 675 | { 676 | Id: 92, 677 | Hex: "#8700d7", 678 | Rgb: Rgb{135, 0, 215}, 679 | Hsl: Hsl{277.674418604651, 100, 42}, 680 | Name: "DarkViolet", 681 | }, 682 | { 683 | Id: 93, 684 | Hex: "#8700ff", 685 | Rgb: Rgb{135, 0, 255}, 686 | Hsl: Hsl{271.764705882353, 100, 50}, 687 | Name: "Purple", 688 | }, 689 | { 690 | Id: 94, 691 | Hex: "#875f00", 692 | Rgb: Rgb{135, 95, 0}, 693 | Hsl: Hsl{42.2222222222222, 100, 26}, 694 | Name: "Orange4", 695 | }, 696 | { 697 | Id: 95, 698 | Hex: "#875f5f", 699 | Rgb: Rgb{135, 95, 95}, 700 | Hsl: Hsl{0, 17, 45}, 701 | Name: "LightPink4", 702 | }, 703 | { 704 | Id: 96, 705 | Hex: "#875f87", 706 | Rgb: Rgb{135, 95, 135}, 707 | Hsl: Hsl{300, 17, 45}, 708 | Name: "Plum4", 709 | }, 710 | { 711 | Id: 97, 712 | Hex: "#875faf", 713 | Rgb: Rgb{135, 95, 175}, 714 | Hsl: Hsl{270, 33, 52}, 715 | Name: "MediumPurple3", 716 | }, 717 | { 718 | Id: 98, 719 | Hex: "#875fd7", 720 | Rgb: Rgb{135, 95, 215}, 721 | Hsl: Hsl{260, 60, 60}, 722 | Name: "MediumPurple3", 723 | }, 724 | { 725 | Id: 99, 726 | Hex: "#875fff", 727 | Rgb: Rgb{135, 95, 255}, 728 | Hsl: Hsl{255, 100, 68}, 729 | Name: "SlateBlue1", 730 | }, 731 | { 732 | Id: 100, 733 | Hex: "#878700", 734 | Rgb: Rgb{135, 135, 0}, 735 | Hsl: Hsl{60, 100, 26}, 736 | Name: "Yellow4", 737 | }, 738 | { 739 | Id: 101, 740 | Hex: "#87875f", 741 | Rgb: Rgb{135, 135, 95}, 742 | Hsl: Hsl{60, 17, 45}, 743 | Name: "Wheat4", 744 | }, 745 | { 746 | Id: 102, 747 | Hex: "#878787", 748 | Rgb: Rgb{135, 135, 135}, 749 | Hsl: Hsl{0, 0, 52}, 750 | Name: "Grey53", 751 | }, 752 | { 753 | Id: 103, 754 | Hex: "#8787af", 755 | Rgb: Rgb{135, 135, 175}, 756 | Hsl: Hsl{240, 20, 60}, 757 | Name: "LightSlateGrey", 758 | }, 759 | { 760 | Id: 104, 761 | Hex: "#8787d7", 762 | Rgb: Rgb{135, 135, 215}, 763 | Hsl: Hsl{240, 50, 68}, 764 | Name: "MediumPurple", 765 | }, 766 | { 767 | Id: 105, 768 | Hex: "#8787ff", 769 | Rgb: Rgb{135, 135, 255}, 770 | Hsl: Hsl{240, 100, 76}, 771 | Name: "LightSlateBlue", 772 | }, 773 | { 774 | Id: 106, 775 | Hex: "#87af00", 776 | Rgb: Rgb{135, 175, 0}, 777 | Hsl: Hsl{73.7142857142857, 100, 34}, 778 | Name: "Yellow4", 779 | }, 780 | { 781 | Id: 107, 782 | Hex: "#87af5f", 783 | Rgb: Rgb{135, 175, 95}, 784 | Hsl: Hsl{90, 33, 52}, 785 | Name: "DarkOliveGreen3", 786 | }, 787 | { 788 | Id: 108, 789 | Hex: "#87af87", 790 | Rgb: Rgb{135, 175, 135}, 791 | Hsl: Hsl{120, 20, 60}, 792 | Name: "DarkSeaGreen", 793 | }, 794 | { 795 | Id: 109, 796 | Hex: "#87afaf", 797 | Rgb: Rgb{135, 175, 175}, 798 | Hsl: Hsl{180, 20, 60}, 799 | Name: "LightSkyBlue3", 800 | }, 801 | { 802 | Id: 110, 803 | Hex: "#87afd7", 804 | Rgb: Rgb{135, 175, 215}, 805 | Hsl: Hsl{210, 50, 68}, 806 | Name: "LightSkyBlue3", 807 | }, 808 | { 809 | Id: 111, 810 | Hex: "#87afff", 811 | Rgb: Rgb{135, 175, 255}, 812 | Hsl: Hsl{220, 100, 76}, 813 | Name: "SkyBlue2", 814 | }, 815 | { 816 | Id: 112, 817 | Hex: "#87d700", 818 | Rgb: Rgb{135, 215, 0}, 819 | Hsl: Hsl{82.3255813953488, 100, 42}, 820 | Name: "Chartreuse2", 821 | }, 822 | { 823 | Id: 113, 824 | Hex: "#87d75f", 825 | Rgb: Rgb{135, 215, 95}, 826 | Hsl: Hsl{100, 60, 60}, 827 | Name: "DarkOliveGreen3", 828 | }, 829 | { 830 | Id: 114, 831 | Hex: "#87d787", 832 | Rgb: Rgb{135, 215, 135}, 833 | Hsl: Hsl{120, 50, 68}, 834 | Name: "PaleGreen3", 835 | }, 836 | { 837 | Id: 115, 838 | Hex: "#87d7af", 839 | Rgb: Rgb{135, 215, 175}, 840 | Hsl: Hsl{150, 50, 68}, 841 | Name: "DarkSeaGreen3", 842 | }, 843 | { 844 | Id: 116, 845 | Hex: "#87d7d7", 846 | Rgb: Rgb{135, 215, 215}, 847 | Hsl: Hsl{180, 50, 68}, 848 | Name: "DarkSlateGray3", 849 | }, 850 | { 851 | Id: 117, 852 | Hex: "#87d7ff", 853 | Rgb: Rgb{135, 215, 255}, 854 | Hsl: Hsl{200, 100, 76}, 855 | Name: "SkyBlue1", 856 | }, 857 | { 858 | Id: 118, 859 | Hex: "#87ff00", 860 | Rgb: Rgb{135, 255, 0}, 861 | Hsl: Hsl{88.2352941176471, 100, 50}, 862 | Name: "Chartreuse1", 863 | }, 864 | { 865 | Id: 119, 866 | Hex: "#87ff5f", 867 | Rgb: Rgb{135, 255, 95}, 868 | Hsl: Hsl{105, 100, 68}, 869 | Name: "LightGreen", 870 | }, 871 | { 872 | Id: 120, 873 | Hex: "#87ff87", 874 | Rgb: Rgb{135, 255, 135}, 875 | Hsl: Hsl{120, 100, 76}, 876 | Name: "LightGreen", 877 | }, 878 | { 879 | Id: 121, 880 | Hex: "#87ffaf", 881 | Rgb: Rgb{135, 255, 175}, 882 | Hsl: Hsl{140, 100, 76}, 883 | Name: "PaleGreen1", 884 | }, 885 | { 886 | Id: 122, 887 | Hex: "#87ffd7", 888 | Rgb: Rgb{135, 255, 215}, 889 | Hsl: Hsl{160, 100, 76}, 890 | Name: "Aquamarine1", 891 | }, 892 | { 893 | Id: 123, 894 | Hex: "#87ffff", 895 | Rgb: Rgb{135, 255, 255}, 896 | Hsl: Hsl{180, 100, 76}, 897 | Name: "DarkSlateGray1", 898 | }, 899 | { 900 | Id: 124, 901 | Hex: "#af0000", 902 | Rgb: Rgb{175, 0, 0}, 903 | Hsl: Hsl{0, 100, 34}, 904 | Name: "Red3", 905 | }, 906 | { 907 | Id: 125, 908 | Hex: "#af005f", 909 | Rgb: Rgb{175, 0, 95}, 910 | Hsl: Hsl{327.428571428571, 100, 34}, 911 | Name: "DeepPink4", 912 | }, 913 | { 914 | Id: 126, 915 | Hex: "#af0087", 916 | Rgb: Rgb{175, 0, 135}, 917 | Hsl: Hsl{313.714285714286, 100, 34}, 918 | Name: "MediumVioletRed", 919 | }, 920 | { 921 | Id: 127, 922 | Hex: "#af00af", 923 | Rgb: Rgb{175, 0, 175}, 924 | Hsl: Hsl{300, 100, 34}, 925 | Name: "Magenta3", 926 | }, 927 | { 928 | Id: 128, 929 | Hex: "#af00d7", 930 | Rgb: Rgb{175, 0, 215}, 931 | Hsl: Hsl{288.837209302326, 100, 42}, 932 | Name: "DarkViolet", 933 | }, 934 | { 935 | Id: 129, 936 | Hex: "#af00ff", 937 | Rgb: Rgb{175, 0, 255}, 938 | Hsl: Hsl{281.176470588235, 100, 50}, 939 | Name: "Purple", 940 | }, 941 | { 942 | Id: 130, 943 | Hex: "#af5f00", 944 | Rgb: Rgb{175, 95, 0}, 945 | Hsl: Hsl{32.5714285714286, 100, 34}, 946 | Name: "DarkOrange3", 947 | }, 948 | { 949 | Id: 131, 950 | Hex: "#af5f5f", 951 | Rgb: Rgb{175, 95, 95}, 952 | Hsl: Hsl{0, 33, 52}, 953 | Name: "IndianRed", 954 | }, 955 | { 956 | Id: 132, 957 | Hex: "#af5f87", 958 | Rgb: Rgb{175, 95, 135}, 959 | Hsl: Hsl{330, 33, 52}, 960 | Name: "HotPink3", 961 | }, 962 | { 963 | Id: 133, 964 | Hex: "#af5faf", 965 | Rgb: Rgb{175, 95, 175}, 966 | Hsl: Hsl{300, 33, 52}, 967 | Name: "MediumOrchid3", 968 | }, 969 | { 970 | Id: 134, 971 | Hex: "#af5fd7", 972 | Rgb: Rgb{175, 95, 215}, 973 | Hsl: Hsl{280, 60, 60}, 974 | Name: "MediumOrchid", 975 | }, 976 | { 977 | Id: 135, 978 | Hex: "#af5fff", 979 | Rgb: Rgb{175, 95, 255}, 980 | Hsl: Hsl{270, 100, 68}, 981 | Name: "MediumPurple2", 982 | }, 983 | { 984 | Id: 136, 985 | Hex: "#af8700", 986 | Rgb: Rgb{175, 135, 0}, 987 | Hsl: Hsl{46.2857142857143, 100, 34}, 988 | Name: "DarkGoldenrod", 989 | }, 990 | { 991 | Id: 137, 992 | Hex: "#af875f", 993 | Rgb: Rgb{175, 135, 95}, 994 | Hsl: Hsl{30, 33, 52}, 995 | Name: "LightSalmon3", 996 | }, 997 | { 998 | Id: 138, 999 | Hex: "#af8787", 1000 | Rgb: Rgb{175, 135, 135}, 1001 | Hsl: Hsl{0, 20, 60}, 1002 | Name: "RosyBrown", 1003 | }, 1004 | { 1005 | Id: 139, 1006 | Hex: "#af87af", 1007 | Rgb: Rgb{175, 135, 175}, 1008 | Hsl: Hsl{300, 20, 60}, 1009 | Name: "Grey63", 1010 | }, 1011 | { 1012 | Id: 140, 1013 | Hex: "#af87d7", 1014 | Rgb: Rgb{175, 135, 215}, 1015 | Hsl: Hsl{270, 50, 68}, 1016 | Name: "MediumPurple2", 1017 | }, 1018 | { 1019 | Id: 141, 1020 | Hex: "#af87ff", 1021 | Rgb: Rgb{175, 135, 255}, 1022 | Hsl: Hsl{260, 100, 76}, 1023 | Name: "MediumPurple1", 1024 | }, 1025 | { 1026 | Id: 142, 1027 | Hex: "#afaf00", 1028 | Rgb: Rgb{175, 175, 0}, 1029 | Hsl: Hsl{60, 100, 34}, 1030 | Name: "Gold3", 1031 | }, 1032 | { 1033 | Id: 143, 1034 | Hex: "#afaf5f", 1035 | Rgb: Rgb{175, 175, 95}, 1036 | Hsl: Hsl{60, 33, 52}, 1037 | Name: "DarkKhaki", 1038 | }, 1039 | { 1040 | Id: 144, 1041 | Hex: "#afaf87", 1042 | Rgb: Rgb{175, 175, 135}, 1043 | Hsl: Hsl{60, 20, 60}, 1044 | Name: "NavajoWhite3", 1045 | }, 1046 | { 1047 | Id: 145, 1048 | Hex: "#afafaf", 1049 | Rgb: Rgb{175, 175, 175}, 1050 | Hsl: Hsl{0, 0, 68}, 1051 | Name: "Grey69", 1052 | }, 1053 | { 1054 | Id: 146, 1055 | Hex: "#afafd7", 1056 | Rgb: Rgb{175, 175, 215}, 1057 | Hsl: Hsl{240, 33, 76}, 1058 | Name: "LightSteelBlue3", 1059 | }, 1060 | { 1061 | Id: 147, 1062 | Hex: "#afafff", 1063 | Rgb: Rgb{175, 175, 255}, 1064 | Hsl: Hsl{240, 100, 84}, 1065 | Name: "LightSteelBlue", 1066 | }, 1067 | { 1068 | Id: 148, 1069 | Hex: "#afd700", 1070 | Rgb: Rgb{175, 215, 0}, 1071 | Hsl: Hsl{71.1627906976744, 100, 42}, 1072 | Name: "Yellow3", 1073 | }, 1074 | { 1075 | Id: 149, 1076 | Hex: "#afd75f", 1077 | Rgb: Rgb{175, 215, 95}, 1078 | Hsl: Hsl{80, 60, 60}, 1079 | Name: "DarkOliveGreen3", 1080 | }, 1081 | { 1082 | Id: 150, 1083 | Hex: "#afd787", 1084 | Rgb: Rgb{175, 215, 135}, 1085 | Hsl: Hsl{90, 50, 68}, 1086 | Name: "DarkSeaGreen3", 1087 | }, 1088 | { 1089 | Id: 151, 1090 | Hex: "#afd7af", 1091 | Rgb: Rgb{175, 215, 175}, 1092 | Hsl: Hsl{120, 33, 76}, 1093 | Name: "DarkSeaGreen2", 1094 | }, 1095 | { 1096 | Id: 152, 1097 | Hex: "#afd7d7", 1098 | Rgb: Rgb{175, 215, 215}, 1099 | Hsl: Hsl{180, 33, 76}, 1100 | Name: "LightCyan3", 1101 | }, 1102 | { 1103 | Id: 153, 1104 | Hex: "#afd7ff", 1105 | Rgb: Rgb{175, 215, 255}, 1106 | Hsl: Hsl{210, 100, 84}, 1107 | Name: "LightSkyBlue1", 1108 | }, 1109 | { 1110 | Id: 154, 1111 | Hex: "#afff00", 1112 | Rgb: Rgb{175, 255, 0}, 1113 | Hsl: Hsl{78.8235294117647, 100, 50}, 1114 | Name: "GreenYellow", 1115 | }, 1116 | { 1117 | Id: 155, 1118 | Hex: "#afff5f", 1119 | Rgb: Rgb{175, 255, 95}, 1120 | Hsl: Hsl{90, 100, 68}, 1121 | Name: "DarkOliveGreen2", 1122 | }, 1123 | { 1124 | Id: 156, 1125 | Hex: "#afff87", 1126 | Rgb: Rgb{175, 255, 135}, 1127 | Hsl: Hsl{100, 100, 76}, 1128 | Name: "PaleGreen1", 1129 | }, 1130 | { 1131 | Id: 157, 1132 | Hex: "#afffaf", 1133 | Rgb: Rgb{175, 255, 175}, 1134 | Hsl: Hsl{120, 100, 84}, 1135 | Name: "DarkSeaGreen2", 1136 | }, 1137 | { 1138 | Id: 158, 1139 | Hex: "#afffd7", 1140 | Rgb: Rgb{175, 255, 215}, 1141 | Hsl: Hsl{150, 100, 84}, 1142 | Name: "DarkSeaGreen1", 1143 | }, 1144 | { 1145 | Id: 159, 1146 | Hex: "#afffff", 1147 | Rgb: Rgb{175, 255, 255}, 1148 | Hsl: Hsl{180, 100, 84}, 1149 | Name: "PaleTurquoise1", 1150 | }, 1151 | { 1152 | Id: 160, 1153 | Hex: "#d70000", 1154 | Rgb: Rgb{215, 0, 0}, 1155 | Hsl: Hsl{0, 100, 42}, 1156 | Name: "Red3", 1157 | }, 1158 | { 1159 | Id: 161, 1160 | Hex: "#d7005f", 1161 | Rgb: Rgb{215, 0, 95}, 1162 | Hsl: Hsl{333.488372093023, 100, 42}, 1163 | Name: "DeepPink3", 1164 | }, 1165 | { 1166 | Id: 162, 1167 | Hex: "#d70087", 1168 | Rgb: Rgb{215, 0, 135}, 1169 | Hsl: Hsl{322.325581395349, 100, 42}, 1170 | Name: "DeepPink3", 1171 | }, 1172 | { 1173 | Id: 163, 1174 | Hex: "#d700af", 1175 | Rgb: Rgb{215, 0, 175}, 1176 | Hsl: Hsl{311.162790697674, 100, 42}, 1177 | Name: "Magenta3", 1178 | }, 1179 | { 1180 | Id: 164, 1181 | Hex: "#d700d7", 1182 | Rgb: Rgb{215, 0, 215}, 1183 | Hsl: Hsl{300, 100, 42}, 1184 | Name: "Magenta3", 1185 | }, 1186 | { 1187 | Id: 165, 1188 | Hex: "#d700ff", 1189 | Rgb: Rgb{215, 0, 255}, 1190 | Hsl: Hsl{290.588235294118, 100, 50}, 1191 | Name: "Magenta2", 1192 | }, 1193 | { 1194 | Id: 166, 1195 | Hex: "#d75f00", 1196 | Rgb: Rgb{215, 95, 0}, 1197 | Hsl: Hsl{26.5116279069767, 100, 42}, 1198 | Name: "DarkOrange3", 1199 | }, 1200 | { 1201 | Id: 167, 1202 | Hex: "#d75f5f", 1203 | Rgb: Rgb{215, 95, 95}, 1204 | Hsl: Hsl{0, 60, 60}, 1205 | Name: "IndianRed", 1206 | }, 1207 | { 1208 | Id: 168, 1209 | Hex: "#d75f87", 1210 | Rgb: Rgb{215, 95, 135}, 1211 | Hsl: Hsl{340, 60, 60}, 1212 | Name: "HotPink3", 1213 | }, 1214 | { 1215 | Id: 169, 1216 | Hex: "#d75faf", 1217 | Rgb: Rgb{215, 95, 175}, 1218 | Hsl: Hsl{320, 60, 60}, 1219 | Name: "HotPink2", 1220 | }, 1221 | { 1222 | Id: 170, 1223 | Hex: "#d75fd7", 1224 | Rgb: Rgb{215, 95, 215}, 1225 | Hsl: Hsl{300, 60, 60}, 1226 | Name: "Orchid", 1227 | }, 1228 | { 1229 | Id: 171, 1230 | Hex: "#d75fff", 1231 | Rgb: Rgb{215, 95, 255}, 1232 | Hsl: Hsl{285, 100, 68}, 1233 | Name: "MediumOrchid1", 1234 | }, 1235 | { 1236 | Id: 172, 1237 | Hex: "#d78700", 1238 | Rgb: Rgb{215, 135, 0}, 1239 | Hsl: Hsl{37.6744186046512, 100, 42}, 1240 | Name: "Orange3", 1241 | }, 1242 | { 1243 | Id: 173, 1244 | Hex: "#d7875f", 1245 | Rgb: Rgb{215, 135, 95}, 1246 | Hsl: Hsl{20, 60, 60}, 1247 | Name: "LightSalmon3", 1248 | }, 1249 | { 1250 | Id: 174, 1251 | Hex: "#d78787", 1252 | Rgb: Rgb{215, 135, 135}, 1253 | Hsl: Hsl{0, 50, 68}, 1254 | Name: "LightPink3", 1255 | }, 1256 | { 1257 | Id: 175, 1258 | Hex: "#d787af", 1259 | Rgb: Rgb{215, 135, 175}, 1260 | Hsl: Hsl{330, 50, 68}, 1261 | Name: "Pink3", 1262 | }, 1263 | { 1264 | Id: 176, 1265 | Hex: "#d787d7", 1266 | Rgb: Rgb{215, 135, 215}, 1267 | Hsl: Hsl{300, 50, 68}, 1268 | Name: "Plum3", 1269 | }, 1270 | { 1271 | Id: 177, 1272 | Hex: "#d787ff", 1273 | Rgb: Rgb{215, 135, 255}, 1274 | Hsl: Hsl{280, 100, 76}, 1275 | Name: "Violet", 1276 | }, 1277 | { 1278 | Id: 178, 1279 | Hex: "#d7af00", 1280 | Rgb: Rgb{215, 175, 0}, 1281 | Hsl: Hsl{48.8372093023256, 100, 42}, 1282 | Name: "Gold3", 1283 | }, 1284 | { 1285 | Id: 179, 1286 | Hex: "#d7af5f", 1287 | Rgb: Rgb{215, 175, 95}, 1288 | Hsl: Hsl{40, 60, 60}, 1289 | Name: "LightGoldenrod3", 1290 | }, 1291 | { 1292 | Id: 180, 1293 | Hex: "#d7af87", 1294 | Rgb: Rgb{215, 175, 135}, 1295 | Hsl: Hsl{30, 50, 68}, 1296 | Name: "Tan", 1297 | }, 1298 | { 1299 | Id: 181, 1300 | Hex: "#d7afaf", 1301 | Rgb: Rgb{215, 175, 175}, 1302 | Hsl: Hsl{0, 33, 76}, 1303 | Name: "MistyRose3", 1304 | }, 1305 | { 1306 | Id: 182, 1307 | Hex: "#d7afd7", 1308 | Rgb: Rgb{215, 175, 215}, 1309 | Hsl: Hsl{300, 33, 76}, 1310 | Name: "Thistle3", 1311 | }, 1312 | { 1313 | Id: 183, 1314 | Hex: "#d7afff", 1315 | Rgb: Rgb{215, 175, 255}, 1316 | Hsl: Hsl{270, 100, 84}, 1317 | Name: "Plum2", 1318 | }, 1319 | { 1320 | Id: 184, 1321 | Hex: "#d7d700", 1322 | Rgb: Rgb{215, 215, 0}, 1323 | Hsl: Hsl{60, 100, 42}, 1324 | Name: "Yellow3", 1325 | }, 1326 | { 1327 | Id: 185, 1328 | Hex: "#d7d75f", 1329 | Rgb: Rgb{215, 215, 95}, 1330 | Hsl: Hsl{60, 60, 60}, 1331 | Name: "Khaki3", 1332 | }, 1333 | { 1334 | Id: 186, 1335 | Hex: "#d7d787", 1336 | Rgb: Rgb{215, 215, 135}, 1337 | Hsl: Hsl{60, 50, 68}, 1338 | Name: "LightGoldenrod2", 1339 | }, 1340 | { 1341 | Id: 187, 1342 | Hex: "#d7d7af", 1343 | Rgb: Rgb{215, 215, 175}, 1344 | Hsl: Hsl{60, 33, 76}, 1345 | Name: "LightYellow3", 1346 | }, 1347 | { 1348 | Id: 188, 1349 | Hex: "#d7d7d7", 1350 | Rgb: Rgb{215, 215, 215}, 1351 | Hsl: Hsl{0, 0, 84}, 1352 | Name: "Grey84", 1353 | }, 1354 | { 1355 | Id: 189, 1356 | Hex: "#d7d7ff", 1357 | Rgb: Rgb{215, 215, 255}, 1358 | Hsl: Hsl{240, 100, 92}, 1359 | Name: "LightSteelBlue1", 1360 | }, 1361 | { 1362 | Id: 190, 1363 | Hex: "#d7ff00", 1364 | Rgb: Rgb{215, 255, 0}, 1365 | Hsl: Hsl{69.4117647058823, 100, 50}, 1366 | Name: "Yellow2", 1367 | }, 1368 | { 1369 | Id: 191, 1370 | Hex: "#d7ff5f", 1371 | Rgb: Rgb{215, 255, 95}, 1372 | Hsl: Hsl{75, 100, 68}, 1373 | Name: "DarkOliveGreen1", 1374 | }, 1375 | { 1376 | Id: 192, 1377 | Hex: "#d7ff87", 1378 | Rgb: Rgb{215, 255, 135}, 1379 | Hsl: Hsl{80, 100, 76}, 1380 | Name: "DarkOliveGreen1", 1381 | }, 1382 | { 1383 | Id: 193, 1384 | Hex: "#d7ffaf", 1385 | Rgb: Rgb{215, 255, 175}, 1386 | Hsl: Hsl{90, 100, 84}, 1387 | Name: "DarkSeaGreen1", 1388 | }, 1389 | { 1390 | Id: 194, 1391 | Hex: "#d7ffd7", 1392 | Rgb: Rgb{215, 255, 215}, 1393 | Hsl: Hsl{120, 100, 92}, 1394 | Name: "Honeydew2", 1395 | }, 1396 | { 1397 | Id: 195, 1398 | Hex: "#d7ffff", 1399 | Rgb: Rgb{215, 255, 255}, 1400 | Hsl: Hsl{180, 100, 92}, 1401 | Name: "LightCyan1", 1402 | }, 1403 | { 1404 | Id: 196, 1405 | Hex: "#ff0000", 1406 | Rgb: Rgb{255, 0, 0}, 1407 | Hsl: Hsl{0, 100, 50}, 1408 | Name: "Red1", 1409 | }, 1410 | { 1411 | Id: 197, 1412 | Hex: "#ff005f", 1413 | Rgb: Rgb{255, 0, 95}, 1414 | Hsl: Hsl{337.647058823529, 100, 50}, 1415 | Name: "DeepPink2", 1416 | }, 1417 | { 1418 | Id: 198, 1419 | Hex: "#ff0087", 1420 | Rgb: Rgb{255, 0, 135}, 1421 | Hsl: Hsl{328.235294117647, 100, 50}, 1422 | Name: "DeepPink1", 1423 | }, 1424 | { 1425 | Id: 199, 1426 | Hex: "#ff00af", 1427 | Rgb: Rgb{255, 0, 175}, 1428 | Hsl: Hsl{318.823529411765, 100, 50}, 1429 | Name: "DeepPink1", 1430 | }, 1431 | { 1432 | Id: 200, 1433 | Hex: "#ff00d7", 1434 | Rgb: Rgb{255, 0, 215}, 1435 | Hsl: Hsl{309.411764705882, 100, 50}, 1436 | Name: "Magenta2", 1437 | }, 1438 | { 1439 | Id: 201, 1440 | Hex: "#ff00ff", 1441 | Rgb: Rgb{255, 0, 255}, 1442 | Hsl: Hsl{300, 100, 50}, 1443 | Name: "Magenta1", 1444 | }, 1445 | { 1446 | Id: 202, 1447 | Hex: "#ff5f00", 1448 | Rgb: Rgb{255, 95, 0}, 1449 | Hsl: Hsl{22.3529411764706, 100, 50}, 1450 | Name: "OrangeRed1", 1451 | }, 1452 | { 1453 | Id: 203, 1454 | Hex: "#ff5f5f", 1455 | Rgb: Rgb{255, 95, 95}, 1456 | Hsl: Hsl{0, 100, 68}, 1457 | Name: "IndianRed1", 1458 | }, 1459 | { 1460 | Id: 204, 1461 | Hex: "#ff5f87", 1462 | Rgb: Rgb{255, 95, 135}, 1463 | Hsl: Hsl{345, 100, 68}, 1464 | Name: "IndianRed1", 1465 | }, 1466 | { 1467 | Id: 205, 1468 | Hex: "#ff5faf", 1469 | Rgb: Rgb{255, 95, 175}, 1470 | Hsl: Hsl{330, 100, 68}, 1471 | Name: "HotPink", 1472 | }, 1473 | { 1474 | Id: 206, 1475 | Hex: "#ff5fd7", 1476 | Rgb: Rgb{255, 95, 215}, 1477 | Hsl: Hsl{315, 100, 68}, 1478 | Name: "HotPink", 1479 | }, 1480 | { 1481 | Id: 207, 1482 | Hex: "#ff5fff", 1483 | Rgb: Rgb{255, 95, 255}, 1484 | Hsl: Hsl{300, 100, 68}, 1485 | Name: "MediumOrchid1", 1486 | }, 1487 | { 1488 | Id: 208, 1489 | Hex: "#ff8700", 1490 | Rgb: Rgb{255, 135, 0}, 1491 | Hsl: Hsl{31.7647058823529, 100, 50}, 1492 | Name: "DarkOrange", 1493 | }, 1494 | { 1495 | Id: 209, 1496 | Hex: "#ff875f", 1497 | Rgb: Rgb{255, 135, 95}, 1498 | Hsl: Hsl{15, 100, 68}, 1499 | Name: "Salmon1", 1500 | }, 1501 | { 1502 | Id: 210, 1503 | Hex: "#ff8787", 1504 | Rgb: Rgb{255, 135, 135}, 1505 | Hsl: Hsl{0, 100, 76}, 1506 | Name: "LightCoral", 1507 | }, 1508 | { 1509 | Id: 211, 1510 | Hex: "#ff87af", 1511 | Rgb: Rgb{255, 135, 175}, 1512 | Hsl: Hsl{340, 100, 76}, 1513 | Name: "PaleVioletRed1", 1514 | }, 1515 | { 1516 | Id: 212, 1517 | Hex: "#ff87d7", 1518 | Rgb: Rgb{255, 135, 215}, 1519 | Hsl: Hsl{320, 100, 76}, 1520 | Name: "Orchid2", 1521 | }, 1522 | { 1523 | Id: 213, 1524 | Hex: "#ff87ff", 1525 | Rgb: Rgb{255, 135, 255}, 1526 | Hsl: Hsl{300, 100, 76}, 1527 | Name: "Orchid1", 1528 | }, 1529 | { 1530 | Id: 214, 1531 | Hex: "#ffaf00", 1532 | Rgb: Rgb{255, 175, 0}, 1533 | Hsl: Hsl{41.1764705882353, 100, 50}, 1534 | Name: "Orange1", 1535 | }, 1536 | { 1537 | Id: 215, 1538 | Hex: "#ffaf5f", 1539 | Rgb: Rgb{255, 175, 95}, 1540 | Hsl: Hsl{30, 100, 68}, 1541 | Name: "SandyBrown", 1542 | }, 1543 | { 1544 | Id: 216, 1545 | Hex: "#ffaf87", 1546 | Rgb: Rgb{255, 175, 135}, 1547 | Hsl: Hsl{20, 100, 76}, 1548 | Name: "LightSalmon1", 1549 | }, 1550 | { 1551 | Id: 217, 1552 | Hex: "#ffafaf", 1553 | Rgb: Rgb{255, 175, 175}, 1554 | Hsl: Hsl{0, 100, 84}, 1555 | Name: "LightPink1", 1556 | }, 1557 | { 1558 | Id: 218, 1559 | Hex: "#ffafd7", 1560 | Rgb: Rgb{255, 175, 215}, 1561 | Hsl: Hsl{330, 100, 84}, 1562 | Name: "Pink1", 1563 | }, 1564 | { 1565 | Id: 219, 1566 | Hex: "#ffafff", 1567 | Rgb: Rgb{255, 175, 255}, 1568 | Hsl: Hsl{300, 100, 84}, 1569 | Name: "Plum1", 1570 | }, 1571 | { 1572 | Id: 220, 1573 | Hex: "#ffd700", 1574 | Rgb: Rgb{255, 215, 0}, 1575 | Hsl: Hsl{50.5882352941176, 100, 50}, 1576 | Name: "Gold1", 1577 | }, 1578 | { 1579 | Id: 221, 1580 | Hex: "#ffd75f", 1581 | Rgb: Rgb{255, 215, 95}, 1582 | Hsl: Hsl{45, 100, 68}, 1583 | Name: "LightGoldenrod2", 1584 | }, 1585 | { 1586 | Id: 222, 1587 | Hex: "#ffd787", 1588 | Rgb: Rgb{255, 215, 135}, 1589 | Hsl: Hsl{40, 100, 76}, 1590 | Name: "LightGoldenrod2", 1591 | }, 1592 | { 1593 | Id: 223, 1594 | Hex: "#ffd7af", 1595 | Rgb: Rgb{255, 215, 175}, 1596 | Hsl: Hsl{30, 100, 84}, 1597 | Name: "NavajoWhite1", 1598 | }, 1599 | { 1600 | Id: 224, 1601 | Hex: "#ffd7d7", 1602 | Rgb: Rgb{255, 215, 215}, 1603 | Hsl: Hsl{0, 100, 92}, 1604 | Name: "MistyRose1", 1605 | }, 1606 | { 1607 | Id: 225, 1608 | Hex: "#ffd7ff", 1609 | Rgb: Rgb{255, 215, 255}, 1610 | Hsl: Hsl{300, 100, 92}, 1611 | Name: "Thistle1", 1612 | }, 1613 | { 1614 | Id: 226, 1615 | Hex: "#ffff00", 1616 | Rgb: Rgb{255, 255, 0}, 1617 | Hsl: Hsl{60, 100, 50}, 1618 | Name: "Yellow1", 1619 | }, 1620 | { 1621 | Id: 227, 1622 | Hex: "#ffff5f", 1623 | Rgb: Rgb{255, 255, 95}, 1624 | Hsl: Hsl{60, 100, 68}, 1625 | Name: "LightGoldenrod1", 1626 | }, 1627 | { 1628 | Id: 228, 1629 | Hex: "#ffff87", 1630 | Rgb: Rgb{255, 255, 135}, 1631 | Hsl: Hsl{60, 100, 76}, 1632 | Name: "Khaki1", 1633 | }, 1634 | { 1635 | Id: 229, 1636 | Hex: "#ffffaf", 1637 | Rgb: Rgb{255, 255, 175}, 1638 | Hsl: Hsl{60, 100, 84}, 1639 | Name: "Wheat1", 1640 | }, 1641 | { 1642 | Id: 230, 1643 | Hex: "#ffffd7", 1644 | Rgb: Rgb{255, 255, 215}, 1645 | Hsl: Hsl{60, 100, 92}, 1646 | Name: "Cornsilk1", 1647 | }, 1648 | { 1649 | Id: 231, 1650 | Hex: "#ffffff", 1651 | Rgb: Rgb{255, 255, 255}, 1652 | Hsl: Hsl{0, 0, 100}, 1653 | Name: "Grey100", 1654 | }, 1655 | { 1656 | Id: 232, 1657 | Hex: "#080808", 1658 | Rgb: Rgb{8, 8, 8}, 1659 | Hsl: Hsl{0, 0, 3}, 1660 | Name: "Grey3", 1661 | }, 1662 | { 1663 | Id: 233, 1664 | Hex: "#121212", 1665 | Rgb: Rgb{18, 18, 18}, 1666 | Hsl: Hsl{0, 0, 7}, 1667 | Name: "Grey7", 1668 | }, 1669 | { 1670 | Id: 234, 1671 | Hex: "#1c1c1c", 1672 | Rgb: Rgb{28, 28, 28}, 1673 | Hsl: Hsl{0, 0, 10}, 1674 | Name: "Grey11", 1675 | }, 1676 | { 1677 | Id: 235, 1678 | Hex: "#262626", 1679 | Rgb: Rgb{38, 38, 38}, 1680 | Hsl: Hsl{0, 0, 14}, 1681 | Name: "Grey15", 1682 | }, 1683 | { 1684 | Id: 236, 1685 | Hex: "#303030", 1686 | Rgb: Rgb{48, 48, 48}, 1687 | Hsl: Hsl{0, 0, 18}, 1688 | Name: "Grey19", 1689 | }, 1690 | { 1691 | Id: 237, 1692 | Hex: "#3a3a3a", 1693 | Rgb: Rgb{58, 58, 58}, 1694 | Hsl: Hsl{0, 0, 22}, 1695 | Name: "Grey23", 1696 | }, 1697 | { 1698 | Id: 238, 1699 | Hex: "#444444", 1700 | Rgb: Rgb{68, 68, 68}, 1701 | Hsl: Hsl{0, 0, 26}, 1702 | Name: "Grey27", 1703 | }, 1704 | { 1705 | Id: 239, 1706 | Hex: "#4e4e4e", 1707 | Rgb: Rgb{78, 78, 78}, 1708 | Hsl: Hsl{0, 0, 30}, 1709 | Name: "Grey30", 1710 | }, 1711 | { 1712 | Id: 240, 1713 | Hex: "#585858", 1714 | Rgb: Rgb{88, 88, 88}, 1715 | Hsl: Hsl{0, 0, 34}, 1716 | Name: "Grey35", 1717 | }, 1718 | { 1719 | Id: 241, 1720 | Hex: "#626262", 1721 | Rgb: Rgb{98, 98, 98}, 1722 | Hsl: Hsl{0, 0, 37}, 1723 | Name: "Grey39", 1724 | }, 1725 | { 1726 | Id: 242, 1727 | Hex: "#6c6c6c", 1728 | Rgb: Rgb{108, 108, 108}, 1729 | Hsl: Hsl{0, 0, 40}, 1730 | Name: "Grey42", 1731 | }, 1732 | { 1733 | Id: 243, 1734 | Hex: "#767676", 1735 | Rgb: Rgb{118, 118, 118}, 1736 | Hsl: Hsl{0, 0, 46}, 1737 | Name: "Grey46", 1738 | }, 1739 | { 1740 | Id: 244, 1741 | Hex: "#808080", 1742 | Rgb: Rgb{128, 128, 128}, 1743 | Hsl: Hsl{0, 0, 50}, 1744 | Name: "Grey50", 1745 | }, 1746 | { 1747 | Id: 245, 1748 | Hex: "#8a8a8a", 1749 | Rgb: Rgb{138, 138, 138}, 1750 | Hsl: Hsl{0, 0, 54}, 1751 | Name: "Grey54", 1752 | }, 1753 | { 1754 | Id: 246, 1755 | Hex: "#949494", 1756 | Rgb: Rgb{148, 148, 148}, 1757 | Hsl: Hsl{0, 0, 58}, 1758 | Name: "Grey58", 1759 | }, 1760 | { 1761 | Id: 247, 1762 | Hex: "#9e9e9e", 1763 | Rgb: Rgb{158, 158, 158}, 1764 | Hsl: Hsl{0, 0, 61}, 1765 | Name: "Grey62", 1766 | }, 1767 | { 1768 | Id: 248, 1769 | Hex: "#a8a8a8", 1770 | Rgb: Rgb{168, 168, 168}, 1771 | Hsl: Hsl{0, 0, 65}, 1772 | Name: "Grey66", 1773 | }, 1774 | { 1775 | Id: 249, 1776 | Hex: "#b2b2b2", 1777 | Rgb: Rgb{178, 178, 178}, 1778 | Hsl: Hsl{0, 0, 69}, 1779 | Name: "Grey70", 1780 | }, 1781 | { 1782 | Id: 250, 1783 | Hex: "#bcbcbc", 1784 | Rgb: Rgb{188, 188, 188}, 1785 | Hsl: Hsl{0, 0, 73}, 1786 | Name: "Grey74", 1787 | }, 1788 | { 1789 | Id: 251, 1790 | Hex: "#c6c6c6", 1791 | Rgb: Rgb{198, 198, 198}, 1792 | Hsl: Hsl{0, 0, 77}, 1793 | Name: "Grey78", 1794 | }, 1795 | { 1796 | Id: 252, 1797 | Hex: "#d0d0d0", 1798 | Rgb: Rgb{208, 208, 208}, 1799 | Hsl: Hsl{0, 0, 81}, 1800 | Name: "Grey82", 1801 | }, 1802 | { 1803 | Id: 253, 1804 | Hex: "#dadada", 1805 | Rgb: Rgb{218, 218, 218}, 1806 | Hsl: Hsl{0, 0, 85}, 1807 | Name: "Grey85", 1808 | }, 1809 | { 1810 | Id: 254, 1811 | Hex: "#e4e4e4", 1812 | Rgb: Rgb{228, 228, 228}, 1813 | Hsl: Hsl{0, 0, 89}, 1814 | Name: "Grey89", 1815 | }, 1816 | { 1817 | Id: 255, 1818 | Hex: "#eeeeee", 1819 | Rgb: Rgb{238, 238, 238}, 1820 | Hsl: Hsl{0, 0, 93}, 1821 | Name: "Grey93", 1822 | }, 1823 | } 1824 | --------------------------------------------------------------------------------