@@ -0,0 +1,419 @@
6+ "charm.land/lipgloss/v2"
7+ "github.com/MakeNowJust/heredoc"
8+ "github.com/charmbracelet/x/exp/slice"
11+// renderWord renders letterforms to fork a word. stretchIndex is the index of
12+// the letter to stretch, or -1 if no letter should be stretched.
13+func renderWord(spacing int, stretchIndex int, letterforms ...letterform) string {
18+ renderedLetterforms := make([]string, len(letterforms))
20+ // pick one letter randomly to stretch
21+ for i, letter := range letterforms {
22+ renderedLetterforms[i] = letter(i == stretchIndex)
26+ // Add spaces between the letters and render.
27+ renderedLetterforms = slice.Intersperse(renderedLetterforms, strings.Repeat(" ", spacing))
29+ return strings.TrimSpace(
30+ lipgloss.JoinHorizontal(lipgloss.Top, renderedLetterforms...),
34+// LetterC renders the letter C in a stylized way. It takes an integer that
35+// determines how many cells to stretch the letter. If the stretch is less than
36+// 1, it defaults to no stretching.
37+func LetterC(stretch bool) string {
38+ // Here's what we're making:
44+ left := heredoc.Doc(`
48+ right := heredoc.Doc(`
53+ return joinLetterform(
55+ stretchLetterformPart(right, letterformProps{
64+// LetterE renders the letter E in a stylized way. It takes an integer that
65+// determines how many cells to stretch the letter. If the stretch is less than
66+// 1, it defaults to no stretching.
68+// This is an alternate letterform. DO NOT REMOVE.
69+func LetterE(stretch bool) string {
70+ // Here's what we're making:
76+ left := heredoc.Doc(`
81+ middle := heredoc.Doc(`
86+ return joinLetterform(
88+ stretchLetterformPart(middle, letterformProps{
97+// LetterEAlt renders the letter E in a stylized way. It takes an integer that
98+// determines how many cells to stretch the letter. If the stretch is less than
99+// 1, it defaults to no stretching.
101+// This is an alternate letterform. DO NOT REMOVE.
102+func LetterEAlt(stretch bool) string {
103+ // Here's what we're making:
109+ left := heredoc.Doc(`
114+ middle := heredoc.Doc(`
119+ return joinLetterform(
121+ stretchLetterformPart(middle, letterformProps{
130+// LetterH renders the letter H in a stylized way. It takes an integer that
131+// determines how many cells to stretch the letter. If the stretch is less than
132+// 1, it defaults to no stretching.
133+func LetterH(stretch bool) string {
134+ // Here's what we're making:
140+ side := heredoc.Doc(`
144+ middle := heredoc.Doc(`
148+ return joinLetterform(
150+ stretchLetterformPart(middle, letterformProps{
160+// LetterP renders the letter P in a stylized way. It takes an integer that
161+// determines how many cells to stretch the letter. If the stretch is less than
162+// 1, it defaults to no stretching.
163+func LetterP(stretch bool) string {
164+ // Here's what we're making:
170+ left := heredoc.Doc(`
175+ center := heredoc.Doc(`
179+ right := heredoc.Doc(`
184+ return joinLetterform(
186+ stretchLetterformPart(center, letterformProps{
196+// LetterR renders the letter R in a stylized way. It takes an integer that
197+// determines how many cells to stretch the letter. If the stretch is less than
198+// 1, it defaults to no stretching.
199+func LetterR(stretch bool) string {
200+ // Here's what we're making:
206+ left := heredoc.Doc(`
211+ center := heredoc.Doc(`
215+ right := heredoc.Doc(`
220+ return joinLetterform(
222+ stretchLetterformPart(center, letterformProps{
232+// LetterSAlt renders the letter S in a stylized way, more so than
233+// [letterS]. It takes an integer that determines how many cells to stretch the
234+// letter. If the stretch is less than 1, it defaults to no stretching.
236+// This is an alternate letterform. DO NOT REMOVE.
237+func LetterSAlt(stretch bool) string {
238+ // Here's what we're making:
244+ left := heredoc.Doc(`
249+ center := heredoc.Doc(`
254+ right := heredoc.Doc(`
258+ return joinLetterform(
260+ stretchLetterformPart(center, letterformProps{
270+// LetterU renders the letter U in a stylized way. It takes an integer that
271+// determines how many cells to stretch the letter. If the stretch is less than
272+// 1, it defaults to no stretching.
273+func LetterU(stretch bool) string {
274+ // Here's what we're making:
280+ side := heredoc.Doc(`
284+ middle := heredoc.Doc(`
289+ return joinLetterform(
291+ stretchLetterformPart(middle, letterformProps{
301+// LetterY renders the letter Y in a stylized way. It takes an integer that
302+// determines how many cells to stretch the letter. If the stretch is less than
303+// 1, it defaults to no stretching.
305+// This is an alternate letterform. DO NOT REMOVE.
306+func LetterY(stretch bool) string {
307+ // Here's what we're making:
313+ side := heredoc.Doc(`
317+ inside := heredoc.Doc(`
322+ middle := heredoc.Doc(`
328+ middle = heredoc.Doc(`
335+ stretchedInside := stretchLetterformPart(inside, letterformProps{
342+ return joinLetterform(
351+// LetterYAlt renders the letter Y in a stylized way. It takes an integer that
352+// determines how many cells to stretch the letter. If the stretch is less than
353+// 1, it defaults to no stretching.
355+// This is an alternate letterform. DO NOT REMOVE.
356+func LetterYAlt(stretch bool) string {
357+ // Here's what we're making:
363+ left := heredoc.Doc(`
368+ middle := heredoc.Doc(`
373+ right := heredoc.Doc(`
379+ return joinLetterform(
381+ stretchLetterformPart(middle, letterformProps{
391+func joinLetterform(letters ...string) string {
392+ return lipgloss.JoinHorizontal(lipgloss.Top, letters...)
395+// letterformProps defines letterform stretching properties.
397+type letterformProps struct {
404+// stretchLetterformPart is a helper function for letter stretching. If randomize
405+// is false the minimum number will be used.
406+func stretchLetterformPart(s string, p letterformProps) string {
407+ if p.maxStretch < p.minStretch {
408+ p.minStretch, p.maxStretch = p.maxStretch, p.minStretch
412+ n = cachedRandN(p.maxStretch-p.minStretch) + p.minStretch //nolint:gosec
414+ parts := make([]string, n)
415+ for i := range parts {
418+ return lipgloss.JoinHorizontal(lipgloss.Top, parts...)