grad.go

  1package styles
  2
  3import (
  4	"fmt"
  5	"image/color"
  6	"strings"
  7
  8	"github.com/lucasb-eyer/go-colorful"
  9	"github.com/rivo/uniseg"
 10)
 11
 12// ForegroundGrad returns a slice of strings representing the input string
 13// rendered with a horizontal gradient foreground from color1 to color2. Each
 14// string in the returned slice corresponds to a grapheme cluster in the input
 15// string. If bold is true, the rendered strings will be bolded.
 16func ForegroundGrad(t *Styles, input string, bold bool, color1, color2 color.Color) []string {
 17	if input == "" {
 18		return []string{""}
 19	}
 20	if len(input) == 1 {
 21		style := t.Base.Foreground(color1)
 22		if bold {
 23			style.Bold(true)
 24		}
 25		return []string{style.Render(input)}
 26	}
 27	var clusters []string
 28	gr := uniseg.NewGraphemes(input)
 29	for gr.Next() {
 30		clusters = append(clusters, string(gr.Runes()))
 31	}
 32
 33	ramp := blendColors(len(clusters), color1, color2)
 34	for i, c := range ramp {
 35		style := t.Base.Foreground(c)
 36		if bold {
 37			style.Bold(true)
 38		}
 39		clusters[i] = style.Render(clusters[i])
 40	}
 41	return clusters
 42}
 43
 44// ApplyForegroundGrad renders a given string with a horizontal gradient
 45// foreground.
 46func ApplyForegroundGrad(t *Styles, input string, color1, color2 color.Color) string {
 47	if input == "" {
 48		return ""
 49	}
 50	var o strings.Builder
 51	clusters := ForegroundGrad(t, input, false, color1, color2)
 52	for _, c := range clusters {
 53		fmt.Fprint(&o, c)
 54	}
 55	return o.String()
 56}
 57
 58// ApplyBoldForegroundGrad renders a given string with a horizontal gradient
 59// foreground.
 60func ApplyBoldForegroundGrad(t *Styles, input string, color1, color2 color.Color) string {
 61	if input == "" {
 62		return ""
 63	}
 64	var o strings.Builder
 65	clusters := ForegroundGrad(t, input, true, color1, color2)
 66	for _, c := range clusters {
 67		fmt.Fprint(&o, c)
 68	}
 69	return o.String()
 70}
 71
 72// blendColors returns a slice of colors blended between the given keys.
 73// Blending is done in Hcl to stay in gamut.
 74func blendColors(size int, stops ...color.Color) []color.Color {
 75	if len(stops) < 2 {
 76		return nil
 77	}
 78
 79	stopsPrime := make([]colorful.Color, len(stops))
 80	for i, k := range stops {
 81		stopsPrime[i], _ = colorful.MakeColor(k)
 82	}
 83
 84	numSegments := len(stopsPrime) - 1
 85	blended := make([]color.Color, 0, size)
 86
 87	// Calculate how many colors each segment should have.
 88	segmentSizes := make([]int, numSegments)
 89	baseSize := size / numSegments
 90	remainder := size % numSegments
 91
 92	// Distribute the remainder across segments.
 93	for i := range numSegments {
 94		segmentSizes[i] = baseSize
 95		if i < remainder {
 96			segmentSizes[i]++
 97		}
 98	}
 99
100	// Generate colors for each segment.
101	for i := range numSegments {
102		c1 := stopsPrime[i]
103		c2 := stopsPrime[i+1]
104		segmentSize := segmentSizes[i]
105
106		for j := range segmentSize {
107			var t float64
108			if segmentSize > 1 {
109				t = float64(j) / float64(segmentSize-1)
110			}
111			c := c1.BlendHcl(c2, t)
112			blended = append(blended, c)
113		}
114	}
115
116	return blended
117}