package state import "fmt" // HexColor is an internal representation of a hexadecimal color string. // It can take one of these formats: // #RRGGBBAA // #RRGGBB // #RGBA // #RGB // When marshaling, it will always choose the most efficient format, but any format can be used when unmarshaling. type HexColor struct { // R is the red component of the color. R uint8 // G is the green component of the color. G uint8 // B is the blue component of the color. B uint8 // A is the alpha component of the color. A uint8 } // HexColorFromString decodes a hexadecimal string into a hex color. func HexColorFromString(text string) (HexColor, error) { var hex HexColor if err := (&hex).UnmarshalText([]byte(text)); err != nil { return hex, err } return hex, nil } func (h *HexColor) UnmarshalText(text []byte) error { var count, expected int var short bool var scanErr error switch len(text) { case 9: // Long form with alpha: #RRGGBBAA expected = 4 short = false count, scanErr = fmt.Sscanf(string(text), "#%02X%02X%02X%02X", &h.R, &h.G, &h.B, &h.A) case 7: // Long form: #RRGGBB expected = 3 h.A = 0xFF count, scanErr = fmt.Sscanf(string(text), "#%02X%02X%02X", &h.R, &h.G, &h.B) case 5: // Short form with alpha: #RGBA expected = 4 short = true count, scanErr = fmt.Sscanf(string(text), "#%01X%01X%01X%01X", &h.R, &h.G, &h.B, &h.A) case 4: // Short form: #RGB expected = 3 h.A = 0xF short = true count, scanErr = fmt.Sscanf(string(text), "#%01X%01X%01X", &h.R, &h.G, &h.B) default: return fmt.Errorf("can't decode %s as HexColor: wrong length", text) } if scanErr != nil { return scanErr } if count != expected { return fmt.Errorf("can't decode %s as HexColor: missing components", text) } if short { h.R *= 0x11 h.G *= 0x11 h.B *= 0x11 h.A *= 0x11 } return nil } // MarshalText marshals the HexColor into a small string. func (h HexColor) MarshalText() (text []byte, err error) { return []byte(h.String()), nil } // String prints the HexColor as an abbreviated notation. // Specifically, short form is used when possible (i.e., when all components are evenly divisible by 0x11). // The alpha component is left out if it's 0xFF. func (h HexColor) String() string { if h.R%0x11 == 0 && h.G%0x11 == 0 && h.B%0x11 == 0 && h.A%0x11 == 0 { // Short form works. if h.A == 0xFF { // It's great when it's easy! return fmt.Sprintf("#%01X%01X%01X", h.R/0x11, h.G/0x11, h.B/0x11) } else { // Just need to add the alpha. return fmt.Sprintf("#%01X%01X%01X%01X", h.R/0x11, h.G/0x11, h.B/0x11, h.A/0x11) } } else { // Gotta use long form. if h.A == 0xFF { // Can skip the alpha channel, though. return fmt.Sprintf("#%02X%02X%02X", h.R, h.G, h.B) } else { // Doing things the hard way. return fmt.Sprintf("#%02X%02X%02X%02X", h.R, h.G, h.B, h.A) } } }