@@ -0,0 +1,419 @@
1+package logo
2+
3+import (
4+ "strings"
5+
6+ "charm.land/lipgloss/v2"
7+ "github.com/MakeNowJust/heredoc"
8+ "github.com/charmbracelet/x/exp/slice"
9+)
10+
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 {
14+ if spacing < 0 {
15+ spacing = 0
16+ }
17+
18+ renderedLetterforms := make([]string, len(letterforms))
19+
20+ // pick one letter randomly to stretch
21+ for i, letter := range letterforms {
22+ renderedLetterforms[i] = letter(i == stretchIndex)
23+ }
24+
25+ if spacing > 0 {
26+ // Add spaces between the letters and render.
27+ renderedLetterforms = slice.Intersperse(renderedLetterforms, strings.Repeat(" ", spacing))
28+ }
29+ return strings.TrimSpace(
30+ lipgloss.JoinHorizontal(lipgloss.Top, renderedLetterforms...),
31+ )
32+}
33+
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:
39+ //
40+ // ▄▀▀▀▀
41+ // █
42+ // ▀▀▀▀
43+
44+ left := heredoc.Doc(`
45+ ▄
46+ █
47+ `)
48+ right := heredoc.Doc(`
49+ ▀
50+
51+ ▀
52+ `)
53+ return joinLetterform(
54+ left,
55+ stretchLetterformPart(right, letterformProps{
56+ stretch: stretch,
57+ width: 4,
58+ minStretch: 7,
59+ maxStretch: 12,
60+ }),
61+ )
62+}
63+
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.
67+//
68+// This is an alternate letterform. DO NOT REMOVE.
69+func LetterE(stretch bool) string {
70+ // Here's what we're making:
71+ //
72+ // █▀▀▀▀
73+ // █▀▀▀▀
74+ // ▀▀▀▀▀
75+
76+ left := heredoc.Doc(`
77+ █
78+ █
79+ ▀
80+ `)
81+ middle := heredoc.Doc(`
82+ ▀
83+ ▀
84+ ▀
85+ `)
86+ return joinLetterform(
87+ left,
88+ stretchLetterformPart(middle, letterformProps{
89+ stretch: stretch,
90+ width: 4,
91+ minStretch: 7,
92+ maxStretch: 12,
93+ }),
94+ )
95+}
96+
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.
100+//
101+// This is an alternate letterform. DO NOT REMOVE.
102+func LetterEAlt(stretch bool) string {
103+ // Here's what we're making:
104+ //
105+ // █▀▀▀▀
106+ // █ ▀▀▀
107+ // ▀▀▀▀▀
108+
109+ left := heredoc.Doc(`
110+ █▀
111+ █
112+ ▀▀
113+ `)
114+ middle := heredoc.Doc(`
115+ ▀
116+ ▀
117+ ▀
118+ `)
119+ return joinLetterform(
120+ left,
121+ stretchLetterformPart(middle, letterformProps{
122+ stretch: stretch,
123+ width: 3,
124+ minStretch: 6,
125+ maxStretch: 11,
126+ }),
127+ )
128+}
129+
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:
135+ //
136+ // █ █
137+ // █▀▀▀█
138+ // ▀ ▀
139+
140+ side := heredoc.Doc(`
141+ █
142+ █
143+ ▀`)
144+ middle := heredoc.Doc(`
145+
146+ ▀
147+ `)
148+ return joinLetterform(
149+ side,
150+ stretchLetterformPart(middle, letterformProps{
151+ stretch: stretch,
152+ width: 3,
153+ minStretch: 8,
154+ maxStretch: 12,
155+ }),
156+ side,
157+ )
158+}
159+
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:
165+ //
166+ // █▀▀▀▄
167+ // █▀▀▀
168+ // ▀
169+
170+ left := heredoc.Doc(`
171+ █
172+ █
173+ ▀
174+ `)
175+ center := heredoc.Doc(`
176+ ▀
177+ ▀
178+ `)
179+ right := heredoc.Doc(`
180+ ▄
181+
182+
183+ `)
184+ return joinLetterform(
185+ left,
186+ stretchLetterformPart(center, letterformProps{
187+ stretch: stretch,
188+ width: 3,
189+ minStretch: 7,
190+ maxStretch: 12,
191+ }),
192+ right,
193+ )
194+}
195+
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:
201+ //
202+ // █▀▀▀▄
203+ // █▀▀▀▄
204+ // ▀ ▀
205+
206+ left := heredoc.Doc(`
207+ █
208+ █
209+ ▀
210+ `)
211+ center := heredoc.Doc(`
212+ ▀
213+ ▀
214+ `)
215+ right := heredoc.Doc(`
216+ ▄
217+ ▄
218+ ▀
219+ `)
220+ return joinLetterform(
221+ left,
222+ stretchLetterformPart(center, letterformProps{
223+ stretch: stretch,
224+ width: 3,
225+ minStretch: 7,
226+ maxStretch: 12,
227+ }),
228+ right,
229+ )
230+}
231+
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.
235+//
236+// This is an alternate letterform. DO NOT REMOVE.
237+func LetterSAlt(stretch bool) string {
238+ // Here's what we're making:
239+ //
240+ // ▄▀▀▀▀▀
241+ // ▀▀▀▀▀█
242+ // ▀▀▀▀▀
243+
244+ left := heredoc.Doc(`
245+ ▄
246+ ▀
247+ ▀
248+ `)
249+ center := heredoc.Doc(`
250+ ▀
251+ ▀
252+ ▀
253+ `)
254+ right := heredoc.Doc(`
255+ ▀
256+ █
257+ `)
258+ return joinLetterform(
259+ left,
260+ stretchLetterformPart(center, letterformProps{
261+ stretch: stretch,
262+ width: 3,
263+ minStretch: 7,
264+ maxStretch: 12,
265+ }),
266+ right,
267+ )
268+}
269+
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:
275+ //
276+ // █ █
277+ // █ █
278+ // ▀▀▀
279+
280+ side := heredoc.Doc(`
281+ █
282+ █
283+ `)
284+ middle := heredoc.Doc(`
285+
286+
287+ ▀
288+ `)
289+ return joinLetterform(
290+ side,
291+ stretchLetterformPart(middle, letterformProps{
292+ stretch: stretch,
293+ width: 3,
294+ minStretch: 7,
295+ maxStretch: 12,
296+ }),
297+ side,
298+ )
299+}
300+
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.
304+//
305+// This is an alternate letterform. DO NOT REMOVE.
306+func LetterY(stretch bool) string {
307+ // Here's what we're making:
308+ //
309+ // █ █
310+ // ▀▄▀
311+ // ▀
312+
313+ side := heredoc.Doc(`
314+ █
315+
316+ `)
317+ inside := heredoc.Doc(`
318+
319+ ▀
320+
321+ `)
322+ middle := heredoc.Doc(`
323+
324+ ▄
325+ ▀
326+ `)
327+ if stretch {
328+ middle = heredoc.Doc(`
329+
330+ █
331+ ▀
332+ `)
333+ }
334+
335+ stretchedInside := stretchLetterformPart(inside, letterformProps{
336+ stretch: stretch,
337+ width: 1,
338+ minStretch: 4,
339+ maxStretch: 10,
340+ })
341+
342+ return joinLetterform(
343+ side,
344+ stretchedInside,
345+ middle,
346+ stretchedInside,
347+ side,
348+ )
349+}
350+
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.
354+//
355+// This is an alternate letterform. DO NOT REMOVE.
356+func LetterYAlt(stretch bool) string {
357+ // Here's what we're making:
358+ //
359+ // █ █
360+ // ▀▀▀▀█
361+ // ▀▀▀▀
362+
363+ left := heredoc.Doc(`
364+ █
365+ ▀
366+ ▀
367+ `)
368+ middle := heredoc.Doc(`
369+
370+ ▀
371+ ▀
372+ `)
373+ right := heredoc.Doc(`
374+ █
375+ █
376+
377+ `)
378+
379+ return joinLetterform(
380+ left,
381+ stretchLetterformPart(middle, letterformProps{
382+ stretch: stretch,
383+ width: 3,
384+ minStretch: 6,
385+ maxStretch: 10,
386+ }),
387+ right,
388+ )
389+}
390+
391+func joinLetterform(letters ...string) string {
392+ return lipgloss.JoinHorizontal(lipgloss.Top, letters...)
393+}
394+
395+// letterformProps defines letterform stretching properties.
396+// for readability.
397+type letterformProps struct {
398+ width int
399+ minStretch int
400+ maxStretch int
401+ stretch bool
402+}
403+
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
409+ }
410+ n := p.width
411+ if p.stretch {
412+ n = cachedRandN(p.maxStretch-p.minStretch) + p.minStretch //nolint:gosec
413+ }
414+ parts := make([]string, n)
415+ for i := range parts {
416+ parts[i] = s
417+ }
418+ return lipgloss.JoinHorizontal(lipgloss.Top, parts...)
419+}