github.com/secoba/wails/v2@v2.6.4/pkg/menu/styledlabel.go (about) 1 package menu 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 ) 8 9 type TextStyle int 10 11 const ( 12 Bold TextStyle = 1 << 0 13 Faint TextStyle = 1 << 1 14 Italic TextStyle = 1 << 2 15 Blinking TextStyle = 1 << 3 16 Inversed TextStyle = 1 << 4 17 Invisible TextStyle = 1 << 5 18 Underlined TextStyle = 1 << 6 19 Strikethrough TextStyle = 1 << 7 20 ) 21 22 type StyledText struct { 23 Label string 24 FgCol *Col 25 BgCol *Col 26 Style TextStyle 27 } 28 29 func (s *StyledText) Bold() bool { 30 return s.Style&Bold == Bold 31 } 32 33 func (s *StyledText) Faint() bool { 34 return s.Style&Faint == Faint 35 } 36 37 func (s *StyledText) Italic() bool { 38 return s.Style&Italic == Italic 39 } 40 41 func (s *StyledText) Blinking() bool { 42 return s.Style&Blinking == Blinking 43 } 44 45 func (s *StyledText) Inversed() bool { 46 return s.Style&Inversed == Inversed 47 } 48 49 func (s *StyledText) Invisible() bool { 50 return s.Style&Invisible == Invisible 51 } 52 53 func (s *StyledText) Underlined() bool { 54 return s.Style&Underlined == Underlined 55 } 56 57 func (s *StyledText) Strikethrough() bool { 58 return s.Style&Strikethrough == Strikethrough 59 } 60 61 var ansiColorMap = map[string]map[string]*Col{ 62 "Normal": { 63 "30": Cols[0], 64 "31": Cols[1], 65 "32": Cols[2], 66 "33": Cols[3], 67 "34": Cols[4], 68 "35": Cols[5], 69 "36": Cols[6], 70 "37": Cols[7], 71 }, 72 "Bold": { 73 "30": Cols[8], 74 "31": Cols[9], 75 "32": Cols[10], 76 "33": Cols[11], 77 "34": Cols[12], 78 "35": Cols[13], 79 "36": Cols[14], 80 "37": Cols[15], 81 }, 82 "Faint": { 83 "30": Cols[0], 84 "31": Cols[1], 85 "32": Cols[2], 86 "33": Cols[3], 87 "34": Cols[4], 88 "35": Cols[5], 89 "36": Cols[6], 90 "37": Cols[7], 91 }, 92 } 93 94 func ParseANSI(input string) ([]*StyledText, error) { 95 var result []*StyledText 96 invalid := fmt.Errorf("invalid ansi string") 97 missingTerminator := fmt.Errorf("missing escape terminator 'm'") 98 invalidTrueColorSequence := fmt.Errorf("invalid TrueColor sequence") 99 invalid256ColSequence := fmt.Errorf("invalid 256 colour sequence") 100 index := 0 101 var currentStyledText *StyledText = &StyledText{} 102 103 if len(input) == 0 { 104 return nil, invalid 105 } 106 107 for { 108 // Read all chars to next escape code 109 esc := strings.Index(input, "\033[") 110 111 // If no more esc chars, save what's left and return 112 if esc == -1 { 113 text := input[index:] 114 if len(text) > 0 { 115 currentStyledText.Label = text 116 result = append(result, currentStyledText) 117 } 118 return result, nil 119 } 120 label := input[:esc] 121 if len(label) > 0 { 122 currentStyledText.Label = label 123 result = append(result, currentStyledText) 124 currentStyledText = &StyledText{ 125 Label: "", 126 FgCol: currentStyledText.FgCol, 127 BgCol: currentStyledText.BgCol, 128 Style: currentStyledText.Style, 129 } 130 } 131 input = input[esc:] 132 // skip 133 input = input[2:] 134 135 // Read in params 136 endesc := strings.Index(input, "m") 137 if endesc == -1 { 138 return nil, missingTerminator 139 } 140 paramText := input[:endesc] 141 input = input[endesc+1:] 142 params := strings.Split(paramText, ";") 143 colourMap := ansiColorMap["Normal"] 144 skip := 0 145 for index, param := range params { 146 if skip > 0 { 147 skip-- 148 continue 149 } 150 switch param { 151 case "0": 152 // Reset styles 153 if len(params) == 1 { 154 if len(currentStyledText.Label) > 0 { 155 result = append(result, currentStyledText) 156 currentStyledText = &StyledText{ 157 Label: "", 158 FgCol: currentStyledText.FgCol, 159 BgCol: currentStyledText.BgCol, 160 Style: currentStyledText.Style, 161 } 162 continue 163 } 164 } 165 currentStyledText.Style = 0 166 currentStyledText.FgCol = nil 167 currentStyledText.BgCol = nil 168 case "1": 169 // Bold 170 colourMap = ansiColorMap["Bold"] 171 currentStyledText.Style |= Bold 172 case "2": 173 // Dim/Feint 174 colourMap = ansiColorMap["Faint"] 175 currentStyledText.Style |= Faint 176 case "3": 177 // Italic 178 currentStyledText.Style |= Italic 179 case "4": 180 // Underlined 181 currentStyledText.Style |= Underlined 182 case "5": 183 // Blinking 184 currentStyledText.Style |= Blinking 185 case "7": 186 // Inverse 187 currentStyledText.Style |= Inversed 188 case "8": 189 // Invisible 190 currentStyledText.Style |= Invisible 191 case "9": 192 // Strikethrough 193 currentStyledText.Style |= Strikethrough 194 case "30", "31", "32", "33", "34", "35", "36", "37": 195 currentStyledText.FgCol = colourMap[param] 196 case "40", "41", "42", "43", "44", "45", "46", "47": 197 currentStyledText.BgCol = colourMap[param] 198 case "38", "48": 199 if len(params)-index < 2 { 200 return nil, invalid 201 } 202 // 256 colours 203 if params[index+1] == "5" { 204 skip = 2 205 colIndexText := params[index+2] 206 colIndex, err := strconv.Atoi(colIndexText) 207 if err != nil { 208 return nil, invalid256ColSequence 209 } 210 if colIndex < 0 || colIndex > 255 { 211 return nil, invalid256ColSequence 212 } 213 if param == "38" { 214 currentStyledText.FgCol = Cols[colIndex] 215 continue 216 } 217 currentStyledText.BgCol = Cols[colIndex] 218 continue 219 } 220 // we must have 4 params left 221 if len(params)-index < 4 { 222 return nil, invalidTrueColorSequence 223 } 224 if params[index+1] != "2" { 225 return nil, invalidTrueColorSequence 226 } 227 var r, g, b uint8 228 ri, err := strconv.Atoi(params[index+2]) 229 if err != nil { 230 return nil, invalidTrueColorSequence 231 } 232 gi, err := strconv.Atoi(params[index+3]) 233 if err != nil { 234 return nil, invalidTrueColorSequence 235 } 236 bi, err := strconv.Atoi(params[index+4]) 237 if err != nil { 238 return nil, invalidTrueColorSequence 239 } 240 if bi > 255 || gi > 255 || ri > 255 { 241 return nil, invalidTrueColorSequence 242 } 243 if bi < 0 || gi < 0 || ri < 0 { 244 return nil, invalidTrueColorSequence 245 } 246 r = uint8(ri) 247 g = uint8(gi) 248 b = uint8(bi) 249 skip = 4 250 colvalue := fmt.Sprintf("#%02x%02x%02x", r, g, b) 251 if param == "38" { 252 currentStyledText.FgCol = &Col{Hex: colvalue, Rgb: Rgb{r, g, b}} 253 continue 254 } 255 currentStyledText.BgCol = &Col{Hex: colvalue} 256 default: 257 return nil, invalid 258 } 259 } 260 } 261 }