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}