From 1fb1fb96029dbcb717c028993a54e617ef46970c Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 5 Jul 2022 16:11:11 -0700 Subject: [PATCH] chore: drop go-colorful dependency (but it's a great lib) --- color.go | 57 ++++++++++++++++++++++++++++++++++++++------------- color_test.go | 34 ++++++++++++++++++++++++++++++ go.mod | 2 -- 3 files changed, 77 insertions(+), 16 deletions(-) diff --git a/color.go b/color.go index 52ec17f..20dafea 100644 --- a/color.go +++ b/color.go @@ -1,9 +1,9 @@ package lipgloss import ( + "image/color" "sync" - "github.com/lucasb-eyer/go-colorful" "github.com/muesli/termenv" ) @@ -144,17 +144,11 @@ func (c Color) color() termenv.Color { // RGBA returns the RGBA value of this color. This satisfies the Go Color // interface. Note that on error we return black with 100% opacity, or: // -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFF. // // This is inline with go-colorful's default behavior. func (c Color) RGBA() (r, g, b, a uint32) { - cf, err := colorful.Hex(c.value()) - if err != nil { - // If we ignore the return behavior and simply return what go-colorful - // give us for the color value we'd be returning exactly this, however - // we're being explicit here for the sake of clarity. - return colorful.Color{}.RGBA() - } + cf := hexToColor(c.value()) return cf.RGBA() } @@ -185,13 +179,48 @@ func (ac AdaptiveColor) color() termenv.Color { // RGBA returns the RGBA value of this color. This satisfies the Go Color // interface. Note that on error we return black with 100% opacity, or: // -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFF. // // This is inline with go-colorful's default behavior. func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) { - cf, err := colorful.Hex(ac.value()) - if err != nil { - return colorful.Color{}.RGBA() - } + cf := hexToColor(ac.value()) return cf.RGBA() } + +// hexToColor translates a hex color string (#RRGGBB or #RGB) into a color.RGB, +// which satisfies the color.Color interface. If an invalid string is passed +// black with 100% opacity will be returned: or, in hex format, 0x000000FF. +func hexToColor(hex string) (c color.RGBA) { + c.A = 0xFF + + if hex == "" || hex[0] != '#' { + return c + } + + switch len(hex) { + case 7: // #RRGGBB + c.R = hexToByte(hex[1])<<4 + hexToByte(hex[2]) + c.G = hexToByte(hex[3])<<4 + hexToByte(hex[4]) + c.B = hexToByte(hex[5])<<4 + hexToByte(hex[6]) + case 4: // #RGB + const offset = 0x11 + c.R = hexToByte(hex[1]) * offset + c.G = hexToByte(hex[2]) * offset + c.B = hexToByte(hex[3]) * offset + } + + return c +} + +func hexToByte(b byte) byte { + switch { + case b >= '0' && b <= '9': + return b - '0' + case b >= 'a' && b <= 'f': + return b - 'a' + 10 + case b >= 'A' && b <= 'F': + return b - 'A' + 10 + } + // Invalid, but just return 0. + return 0 +} diff --git a/color_test.go b/color_test.go index ff522e2..83531e0 100644 --- a/color_test.go +++ b/color_test.go @@ -51,3 +51,37 @@ func TestSetColorProfile(t *testing.T) { } } } + +func TestHexToColor(t *testing.T) { + t.Parallel() + + tt := []struct { + input string + expected uint + }{ + { + "#FF0000", + 0xFF0000, + }, + { + "#00F", + 0x0000FF, + }, + { + "#6B50FF", + 0x6B50FF, + }, + { + "invalid color", + 0x0, + }, + } + + for i, tc := range tt { + h := hexToColor(tc.input) + o := uint(h.R)<<16 + uint(h.G)<<8 + uint(h.B) + if o != tc.expected { + t.Errorf("expected %X, got %X (test #%d)", o, tc.expected, i+1) + } + } +} diff --git a/go.mod b/go.mod index c511c03..9e46b24 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,7 @@ module github.com/charmbracelet/lipgloss go 1.15 require ( - github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mattn/go-runewidth v0.0.13 github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c )