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  }