letterforms.go

  1package logo
  2
  3import (
  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.
 13func 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.
 37func 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.
 69func 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.
102func LetterEAlt(stretch bool) string {
103	// Here's what we're making:
104	//
105	// █▀▀▀▀
106	// █ ▀▀▀
107	// ▀▀▀▀▀
108
109	left := heredoc.Doc(`
110       █▀
111112       ▀▀
113	`)
114	middle := heredoc.Doc(`
115116117118	`)
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.
133func LetterH(stretch bool) string {
134	// Here's what we're making:
135	//
136	// █   █
137	// █▀▀▀█
138	// ▀   ▀
139
140	side := heredoc.Doc(`
141142143`)
144	middle := heredoc.Doc(`
145
146147	`)
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.
163func LetterP(stretch bool) string {
164	// Here's what we're making:
165	//
166	// █▀▀▀▄
167	// █▀▀▀
168	// ▀
169
170	left := heredoc.Doc(`
171172173174	`)
175	center := heredoc.Doc(`
176177178	`)
179	right := heredoc.Doc(`
180181
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.
199func LetterR(stretch bool) string {
200	// Here's what we're making:
201	//
202	// █▀▀▀▄
203	// █▀▀▀▄
204	// ▀   ▀
205
206	left := heredoc.Doc(`
207208209210	`)
211	center := heredoc.Doc(`
212213214	`)
215	right := heredoc.Doc(`
216217218219	`)
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.
237func LetterSAlt(stretch bool) string {
238	// Here's what we're making:
239	//
240	// ▄▀▀▀▀▀
241	// ▀▀▀▀▀█
242	// ▀▀▀▀▀
243
244	left := heredoc.Doc(`
245246247248	`)
249	center := heredoc.Doc(`
250251252253	`)
254	right := heredoc.Doc(`
255256257	`)
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.
273func LetterU(stretch bool) string {
274	// Here's what we're making:
275	//
276	// █   █
277	// █   █
278	//	▀▀▀
279
280	side := heredoc.Doc(`
281282283	`)
284	middle := heredoc.Doc(`
285
286
287288	`)
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.
306func LetterY(stretch bool) string {
307	// Here's what we're making:
308	//
309	// █   █
310	//	▀▄▀
311	//	 ▀
312
313	side := heredoc.Doc(`
314315
316	`)
317	inside := heredoc.Doc(`
318
319320
321	`)
322	middle := heredoc.Doc(`
323
324325326	`)
327	if stretch {
328		middle = heredoc.Doc(`
329
330331332		`)
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.
356func LetterYAlt(stretch bool) string {
357	// Here's what we're making:
358	//
359	// █   █
360	// ▀▀▀▀█
361	// ▀▀▀▀
362
363	left := heredoc.Doc(`
364365366367	`)
368	middle := heredoc.Doc(`
369
370371372	`)
373	right := heredoc.Doc(`
374375376
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
391func joinLetterform(letters ...string) string {
392	return lipgloss.JoinHorizontal(lipgloss.Top, letters...)
393}
394
395// letterformProps defines letterform stretching properties.
396// for readability.
397type 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.
406func 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}