set.go

  1package lipgloss
  2
  3import "image/color"
  4
  5// Set a value on the underlying rules map.
  6func (s *Style) set(key propKey, value any) {
  7	// We don't allow negative integers on any of our other values, so just keep
  8	// them at zero or above. We could use uints instead, but the
  9	// conversions are a little tedious, so we're sticking with ints for
 10	// sake of usability.
 11	switch key { //nolint:exhaustive
 12	case foregroundKey:
 13		s.fgColor = colorOrNil(value)
 14	case backgroundKey:
 15		s.bgColor = colorOrNil(value)
 16	case widthKey:
 17		s.width = max(0, value.(int))
 18	case heightKey:
 19		s.height = max(0, value.(int))
 20	case alignHorizontalKey:
 21		s.alignHorizontal = value.(Position)
 22	case alignVerticalKey:
 23		s.alignVertical = value.(Position)
 24	case paddingTopKey:
 25		s.paddingTop = max(0, value.(int))
 26	case paddingRightKey:
 27		s.paddingRight = max(0, value.(int))
 28	case paddingBottomKey:
 29		s.paddingBottom = max(0, value.(int))
 30	case paddingLeftKey:
 31		s.paddingLeft = max(0, value.(int))
 32	case marginTopKey:
 33		s.marginTop = max(0, value.(int))
 34	case marginRightKey:
 35		s.marginRight = max(0, value.(int))
 36	case marginBottomKey:
 37		s.marginBottom = max(0, value.(int))
 38	case marginLeftKey:
 39		s.marginLeft = max(0, value.(int))
 40	case marginBackgroundKey:
 41		s.marginBgColor = colorOrNil(value)
 42	case borderStyleKey:
 43		s.borderStyle = value.(Border)
 44	case borderTopForegroundKey:
 45		s.borderTopFgColor = colorOrNil(value)
 46	case borderRightForegroundKey:
 47		s.borderRightFgColor = colorOrNil(value)
 48	case borderBottomForegroundKey:
 49		s.borderBottomFgColor = colorOrNil(value)
 50	case borderLeftForegroundKey:
 51		s.borderLeftFgColor = colorOrNil(value)
 52	case borderTopBackgroundKey:
 53		s.borderTopBgColor = colorOrNil(value)
 54	case borderRightBackgroundKey:
 55		s.borderRightBgColor = colorOrNil(value)
 56	case borderBottomBackgroundKey:
 57		s.borderBottomBgColor = colorOrNil(value)
 58	case borderLeftBackgroundKey:
 59		s.borderLeftBgColor = colorOrNil(value)
 60	case maxWidthKey:
 61		s.maxWidth = max(0, value.(int))
 62	case maxHeightKey:
 63		s.maxHeight = max(0, value.(int))
 64	case tabWidthKey:
 65		// TabWidth is the only property that may have a negative value (and
 66		// that negative value can be no less than -1).
 67		s.tabWidth = value.(int)
 68	case transformKey:
 69		s.transform = value.(func(string) string)
 70	default:
 71		if v, ok := value.(bool); ok { //nolint:nestif
 72			if v {
 73				s.attrs |= int(key)
 74			} else {
 75				s.attrs &^= int(key)
 76			}
 77		} else if attrs, ok := value.(int); ok {
 78			// bool attrs
 79			if attrs&int(key) != 0 {
 80				s.attrs |= int(key)
 81			} else {
 82				s.attrs &^= int(key)
 83			}
 84		}
 85	}
 86
 87	// Set the prop on
 88	s.props = s.props.set(key)
 89}
 90
 91// setFrom sets the property from another style.
 92func (s *Style) setFrom(key propKey, i Style) {
 93	switch key { //nolint:exhaustive
 94	case foregroundKey:
 95		s.set(foregroundKey, i.fgColor)
 96	case backgroundKey:
 97		s.set(backgroundKey, i.bgColor)
 98	case widthKey:
 99		s.set(widthKey, i.width)
100	case heightKey:
101		s.set(heightKey, i.height)
102	case alignHorizontalKey:
103		s.set(alignHorizontalKey, i.alignHorizontal)
104	case alignVerticalKey:
105		s.set(alignVerticalKey, i.alignVertical)
106	case paddingTopKey:
107		s.set(paddingTopKey, i.paddingTop)
108	case paddingRightKey:
109		s.set(paddingRightKey, i.paddingRight)
110	case paddingBottomKey:
111		s.set(paddingBottomKey, i.paddingBottom)
112	case paddingLeftKey:
113		s.set(paddingLeftKey, i.paddingLeft)
114	case marginTopKey:
115		s.set(marginTopKey, i.marginTop)
116	case marginRightKey:
117		s.set(marginRightKey, i.marginRight)
118	case marginBottomKey:
119		s.set(marginBottomKey, i.marginBottom)
120	case marginLeftKey:
121		s.set(marginLeftKey, i.marginLeft)
122	case marginBackgroundKey:
123		s.set(marginBackgroundKey, i.marginBgColor)
124	case borderStyleKey:
125		s.set(borderStyleKey, i.borderStyle)
126	case borderTopForegroundKey:
127		s.set(borderTopForegroundKey, i.borderTopFgColor)
128	case borderRightForegroundKey:
129		s.set(borderRightForegroundKey, i.borderRightFgColor)
130	case borderBottomForegroundKey:
131		s.set(borderBottomForegroundKey, i.borderBottomFgColor)
132	case borderLeftForegroundKey:
133		s.set(borderLeftForegroundKey, i.borderLeftFgColor)
134	case borderTopBackgroundKey:
135		s.set(borderTopBackgroundKey, i.borderTopBgColor)
136	case borderRightBackgroundKey:
137		s.set(borderRightBackgroundKey, i.borderRightBgColor)
138	case borderBottomBackgroundKey:
139		s.set(borderBottomBackgroundKey, i.borderBottomBgColor)
140	case borderLeftBackgroundKey:
141		s.set(borderLeftBackgroundKey, i.borderLeftBgColor)
142	case maxWidthKey:
143		s.set(maxWidthKey, i.maxWidth)
144	case maxHeightKey:
145		s.set(maxHeightKey, i.maxHeight)
146	case tabWidthKey:
147		s.set(tabWidthKey, i.tabWidth)
148	case transformKey:
149		s.set(transformKey, i.transform)
150	default:
151		// Set attributes for set bool properties
152		s.set(key, i.attrs)
153	}
154}
155
156func colorOrNil(c any) color.Color {
157	if c, ok := c.(color.Color); ok {
158		return c
159	}
160	return nil
161}
162
163// Bold sets a bold formatting rule.
164func (s Style) Bold(v bool) Style {
165	s.set(boldKey, v)
166	return s
167}
168
169// Italic sets an italic formatting rule. In some terminal emulators this will
170// render with "reverse" coloring if not italic font variant is available.
171func (s Style) Italic(v bool) Style {
172	s.set(italicKey, v)
173	return s
174}
175
176// Underline sets an underline rule. By default, underlines will not be drawn on
177// whitespace like margins and padding. To change this behavior set
178// [Style.UnderlineSpaces].
179func (s Style) Underline(v bool) Style {
180	s.set(underlineKey, v)
181	return s
182}
183
184// Strikethrough sets a strikethrough rule. By default, strikes will not be
185// drawn on whitespace like margins and padding. To change this behavior set
186// StrikethroughSpaces.
187func (s Style) Strikethrough(v bool) Style {
188	s.set(strikethroughKey, v)
189	return s
190}
191
192// Reverse sets a rule for inverting foreground and background colors.
193func (s Style) Reverse(v bool) Style {
194	s.set(reverseKey, v)
195	return s
196}
197
198// Blink sets a rule for blinking foreground text.
199func (s Style) Blink(v bool) Style {
200	s.set(blinkKey, v)
201	return s
202}
203
204// Faint sets a rule for rendering the foreground color in a dimmer shade.
205func (s Style) Faint(v bool) Style {
206	s.set(faintKey, v)
207	return s
208}
209
210// Foreground sets a foreground color.
211//
212//	// Sets the foreground to blue
213//	s := lipgloss.NewStyle().Foreground(lipgloss.Color("#0000ff"))
214//
215//	// Removes the foreground color
216//	s.Foreground(lipgloss.NoColor)
217func (s Style) Foreground(c color.Color) Style {
218	s.set(foregroundKey, c)
219	return s
220}
221
222// Background sets a background color.
223func (s Style) Background(c color.Color) Style {
224	s.set(backgroundKey, c)
225	return s
226}
227
228// Width sets the width of the block before applying margins. This means your
229// styled content will exactly equal the size set here. Text will wrap based on
230// Padding and Borders set on the style.
231func (s Style) Width(i int) Style {
232	s.set(widthKey, i)
233	return s
234}
235
236// Height sets the height of the block before applying margins. If the height of
237// the text block is less than this value after applying padding (or not), the
238// block will be set to this height.
239func (s Style) Height(i int) Style {
240	s.set(heightKey, i)
241	return s
242}
243
244// Align is a shorthand method for setting horizontal and vertical alignment.
245//
246// With one argument, the position value is applied to the horizontal alignment.
247//
248// With two arguments, the value is applied to the horizontal and vertical
249// alignments, in that order.
250func (s Style) Align(p ...Position) Style {
251	if len(p) > 0 {
252		s.set(alignHorizontalKey, p[0])
253	}
254	if len(p) > 1 {
255		s.set(alignVerticalKey, p[1])
256	}
257	return s
258}
259
260// AlignHorizontal sets a horizontal text alignment rule.
261func (s Style) AlignHorizontal(p Position) Style {
262	s.set(alignHorizontalKey, p)
263	return s
264}
265
266// AlignVertical sets a vertical text alignment rule.
267func (s Style) AlignVertical(p Position) Style {
268	s.set(alignVerticalKey, p)
269	return s
270}
271
272// Padding is a shorthand method for setting padding on all sides at once.
273//
274// With one argument, the value is applied to all sides.
275//
276// With two arguments, the value is applied to the vertical and horizontal
277// sides, in that order.
278//
279// With three arguments, the value is applied to the top side, the horizontal
280// sides, and the bottom side, in that order.
281//
282// With four arguments, the value is applied clockwise starting from the top
283// side, followed by the right side, then the bottom, and finally the left.
284//
285// With more than four arguments no padding will be added.
286func (s Style) Padding(i ...int) Style {
287	top, right, bottom, left, ok := whichSidesInt(i...)
288	if !ok {
289		return s
290	}
291
292	s.set(paddingTopKey, top)
293	s.set(paddingRightKey, right)
294	s.set(paddingBottomKey, bottom)
295	s.set(paddingLeftKey, left)
296	return s
297}
298
299// PaddingLeft adds padding on the left.
300func (s Style) PaddingLeft(i int) Style {
301	s.set(paddingLeftKey, i)
302	return s
303}
304
305// PaddingRight adds padding on the right.
306func (s Style) PaddingRight(i int) Style {
307	s.set(paddingRightKey, i)
308	return s
309}
310
311// PaddingTop adds padding to the top of the block.
312func (s Style) PaddingTop(i int) Style {
313	s.set(paddingTopKey, i)
314	return s
315}
316
317// PaddingBottom adds padding to the bottom of the block.
318func (s Style) PaddingBottom(i int) Style {
319	s.set(paddingBottomKey, i)
320	return s
321}
322
323// ColorWhitespace determines whether or not the background color should be
324// applied to the padding. This is true by default as it's more than likely the
325// desired and expected behavior, but it can be disabled for certain graphic
326// effects.
327//
328// Deprecated: Just use margins and padding.
329func (s Style) ColorWhitespace(v bool) Style {
330	s.set(colorWhitespaceKey, v)
331	return s
332}
333
334// Margin is a shorthand method for setting margins on all sides at once.
335//
336// With one argument, the value is applied to all sides.
337//
338// With two arguments, the value is applied to the vertical and horizontal
339// sides, in that order.
340//
341// With three arguments, the value is applied to the top side, the horizontal
342// sides, and the bottom side, in that order.
343//
344// With four arguments, the value is applied clockwise starting from the top
345// side, followed by the right side, then the bottom, and finally the left.
346//
347// With more than four arguments no margin will be added.
348func (s Style) Margin(i ...int) Style {
349	top, right, bottom, left, ok := whichSidesInt(i...)
350	if !ok {
351		return s
352	}
353
354	s.set(marginTopKey, top)
355	s.set(marginRightKey, right)
356	s.set(marginBottomKey, bottom)
357	s.set(marginLeftKey, left)
358	return s
359}
360
361// MarginLeft sets the value of the left margin.
362func (s Style) MarginLeft(i int) Style {
363	s.set(marginLeftKey, i)
364	return s
365}
366
367// MarginRight sets the value of the right margin.
368func (s Style) MarginRight(i int) Style {
369	s.set(marginRightKey, i)
370	return s
371}
372
373// MarginTop sets the value of the top margin.
374func (s Style) MarginTop(i int) Style {
375	s.set(marginTopKey, i)
376	return s
377}
378
379// MarginBottom sets the value of the bottom margin.
380func (s Style) MarginBottom(i int) Style {
381	s.set(marginBottomKey, i)
382	return s
383}
384
385// MarginBackground sets the background color of the margin. Note that this is
386// also set when inheriting from a style with a background color. In that case
387// the background color on that style will set the margin color on this style.
388func (s Style) MarginBackground(c color.Color) Style {
389	s.set(marginBackgroundKey, c)
390	return s
391}
392
393// Border is shorthand for setting the border style and which sides should
394// have a border at once. The variadic argument sides works as follows:
395//
396// With one value, the value is applied to all sides.
397//
398// With two values, the values are applied to the vertical and horizontal
399// sides, in that order.
400//
401// With three values, the values are applied to the top side, the horizontal
402// sides, and the bottom side, in that order.
403//
404// With four values, the values are applied clockwise starting from the top
405// side, followed by the right side, then the bottom, and finally the left.
406//
407// With more than four arguments the border will be applied to all sides.
408//
409// Examples:
410//
411//	// Applies borders to the top and bottom only
412//	lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false)
413//
414//	// Applies rounded borders to the right and bottom only
415//	lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), false, true, true, false)
416func (s Style) Border(b Border, sides ...bool) Style {
417	s.set(borderStyleKey, b)
418
419	top, right, bottom, left, ok := whichSidesBool(sides...)
420	if !ok {
421		top = true
422		right = true
423		bottom = true
424		left = true
425	}
426
427	s.set(borderTopKey, top)
428	s.set(borderRightKey, right)
429	s.set(borderBottomKey, bottom)
430	s.set(borderLeftKey, left)
431
432	return s
433}
434
435// BorderStyle defines the Border on a style. A Border contains a series of
436// definitions for the sides and corners of a border.
437//
438// Note that if border visibility has not been set for any sides when setting
439// the border style, the border will be enabled for all sides during rendering.
440//
441// You can define border characters as you'd like, though several default
442// styles are included: NormalBorder(), RoundedBorder(), BlockBorder(),
443// OuterHalfBlockBorder(), InnerHalfBlockBorder(), ThickBorder(),
444// and DoubleBorder().
445//
446// Example:
447//
448//	lipgloss.NewStyle().BorderStyle(lipgloss.ThickBorder())
449func (s Style) BorderStyle(b Border) Style {
450	s.set(borderStyleKey, b)
451	return s
452}
453
454// BorderTop determines whether or not to draw a top border.
455func (s Style) BorderTop(v bool) Style {
456	s.set(borderTopKey, v)
457	return s
458}
459
460// BorderRight determines whether or not to draw a right border.
461func (s Style) BorderRight(v bool) Style {
462	s.set(borderRightKey, v)
463	return s
464}
465
466// BorderBottom determines whether or not to draw a bottom border.
467func (s Style) BorderBottom(v bool) Style {
468	s.set(borderBottomKey, v)
469	return s
470}
471
472// BorderLeft determines whether or not to draw a left border.
473func (s Style) BorderLeft(v bool) Style {
474	s.set(borderLeftKey, v)
475	return s
476}
477
478// BorderForeground is a shorthand function for setting all of the
479// foreground colors of the borders at once. The arguments work as follows:
480//
481// With one argument, the argument is applied to all sides.
482//
483// With two arguments, the arguments are applied to the vertical and horizontal
484// sides, in that order.
485//
486// With three arguments, the arguments are applied to the top side, the
487// horizontal sides, and the bottom side, in that order.
488//
489// With four arguments, the arguments are applied clockwise starting from the
490// top side, followed by the right side, then the bottom, and finally the left.
491//
492// With more than four arguments nothing will be set.
493func (s Style) BorderForeground(c ...color.Color) Style {
494	if len(c) == 0 {
495		return s
496	}
497
498	top, right, bottom, left, ok := whichSidesColor(c...)
499	if !ok {
500		return s
501	}
502
503	s.set(borderTopForegroundKey, top)
504	s.set(borderRightForegroundKey, right)
505	s.set(borderBottomForegroundKey, bottom)
506	s.set(borderLeftForegroundKey, left)
507
508	return s
509}
510
511// BorderTopForeground set the foreground color for the top of the border.
512func (s Style) BorderTopForeground(c color.Color) Style {
513	s.set(borderTopForegroundKey, c)
514	return s
515}
516
517// BorderRightForeground sets the foreground color for the right side of the
518// border.
519func (s Style) BorderRightForeground(c color.Color) Style {
520	s.set(borderRightForegroundKey, c)
521	return s
522}
523
524// BorderBottomForeground sets the foreground color for the bottom of the
525// border.
526func (s Style) BorderBottomForeground(c color.Color) Style {
527	s.set(borderBottomForegroundKey, c)
528	return s
529}
530
531// BorderLeftForeground sets the foreground color for the left side of the
532// border.
533func (s Style) BorderLeftForeground(c color.Color) Style {
534	s.set(borderLeftForegroundKey, c)
535	return s
536}
537
538// BorderBackground is a shorthand function for setting all of the
539// background colors of the borders at once. The arguments work as follows:
540//
541// With one argument, the argument is applied to all sides.
542//
543// With two arguments, the arguments are applied to the vertical and horizontal
544// sides, in that order.
545//
546// With three arguments, the arguments are applied to the top side, the
547// horizontal sides, and the bottom side, in that order.
548//
549// With four arguments, the arguments are applied clockwise starting from the
550// top side, followed by the right side, then the bottom, and finally the left.
551//
552// With more than four arguments nothing will be set.
553func (s Style) BorderBackground(c ...color.Color) Style {
554	if len(c) == 0 {
555		return s
556	}
557
558	top, right, bottom, left, ok := whichSidesColor(c...)
559	if !ok {
560		return s
561	}
562
563	s.set(borderTopBackgroundKey, top)
564	s.set(borderRightBackgroundKey, right)
565	s.set(borderBottomBackgroundKey, bottom)
566	s.set(borderLeftBackgroundKey, left)
567
568	return s
569}
570
571// BorderTopBackground sets the background color of the top of the border.
572func (s Style) BorderTopBackground(c color.Color) Style {
573	s.set(borderTopBackgroundKey, c)
574	return s
575}
576
577// BorderRightBackground sets the background color of right side the border.
578func (s Style) BorderRightBackground(c color.Color) Style {
579	s.set(borderRightBackgroundKey, c)
580	return s
581}
582
583// BorderBottomBackground sets the background color of the bottom of the
584// border.
585func (s Style) BorderBottomBackground(c color.Color) Style {
586	s.set(borderBottomBackgroundKey, c)
587	return s
588}
589
590// BorderLeftBackground set the background color of the left side of the
591// border.
592func (s Style) BorderLeftBackground(c color.Color) Style {
593	s.set(borderLeftBackgroundKey, c)
594	return s
595}
596
597// Inline makes rendering output one line and disables the rendering of
598// margins, padding and borders. This is useful when you need a style to apply
599// only to font rendering and don't want it to change any physical dimensions.
600// It works well with Style.MaxWidth.
601//
602// Because this in intended to be used at the time of render, this method will
603// not mutate the style and instead return a copy.
604//
605// Example:
606//
607//	var userInput string = "..."
608//	var userStyle = text.Style{ /* ... */ }
609//	fmt.Println(userStyle.Inline(true).Render(userInput))
610func (s Style) Inline(v bool) Style {
611	o := s // copy
612	o.set(inlineKey, v)
613	return o
614}
615
616// MaxWidth applies a max width to a given style. This is useful in enforcing
617// a certain width at render time, particularly with arbitrary strings and
618// styles.
619//
620// Because this in intended to be used at the time of render, this method will
621// not mutate the style and instead return a copy.
622//
623// Example:
624//
625//	var userInput string = "..."
626//	var userStyle = text.Style{ /* ... */ }
627//	fmt.Println(userStyle.MaxWidth(16).Render(userInput))
628func (s Style) MaxWidth(n int) Style {
629	o := s // copy
630	o.set(maxWidthKey, n)
631	return o
632}
633
634// MaxHeight applies a max height to a given style. This is useful in enforcing
635// a certain height at render time, particularly with arbitrary strings and
636// styles.
637//
638// Because this in intended to be used at the time of render, this method will
639// not mutate the style and instead returns a copy.
640func (s Style) MaxHeight(n int) Style {
641	o := s // copy
642	o.set(maxHeightKey, n)
643	return o
644}
645
646// NoTabConversion can be passed to [Style.TabWidth] to disable the replacement
647// of tabs with spaces at render time.
648const NoTabConversion = -1
649
650// TabWidth sets the number of spaces that a tab (/t) should be rendered as.
651// When set to 0, tabs will be removed. To disable the replacement of tabs with
652// spaces entirely, set this to [NoTabConversion].
653//
654// By default, tabs will be replaced with 4 spaces.
655func (s Style) TabWidth(n int) Style {
656	if n <= -1 {
657		n = -1
658	}
659	s.set(tabWidthKey, n)
660	return s
661}
662
663// UnderlineSpaces determines whether to underline spaces between words. By
664// default, this is true. Spaces can also be underlined without underlining the
665// text itself.
666func (s Style) UnderlineSpaces(v bool) Style {
667	s.set(underlineSpacesKey, v)
668	return s
669}
670
671// StrikethroughSpaces determines whether to apply strikethroughs to spaces
672// between words. By default, this is true. Spaces can also be struck without
673// underlining the text itself.
674func (s Style) StrikethroughSpaces(v bool) Style {
675	s.set(strikethroughSpacesKey, v)
676	return s
677}
678
679// Transform applies a given function to a string at render time, allowing for
680// the string being rendered to be manipuated.
681//
682// Example:
683//
684//	s := NewStyle().Transform(strings.ToUpper)
685//	fmt.Println(s.Render("raow!") // "RAOW!"
686func (s Style) Transform(fn func(string) string) Style {
687	s.set(transformKey, fn)
688	return s
689}
690
691// whichSidesInt is a helper method for setting values on sides of a block based
692// on the number of arguments. It follows the CSS shorthand rules for blocks
693// like margin, padding. and borders. Here are how the rules work:
694//
695// 0 args:  do nothing
696// 1 arg:   all sides
697// 2 args:  top -> bottom
698// 3 args:  top -> horizontal -> bottom
699// 4 args:  top -> right -> bottom -> left
700// 5+ args: do nothing.
701func whichSidesInt(i ...int) (top, right, bottom, left int, ok bool) {
702	switch len(i) {
703	case 1:
704		top = i[0]
705		bottom = i[0]
706		left = i[0]
707		right = i[0]
708		ok = true
709	case 2: //nolint:mnd
710		top = i[0]
711		bottom = i[0]
712		left = i[1]
713		right = i[1]
714		ok = true
715	case 3: //nolint:mnd
716		top = i[0]
717		left = i[1]
718		right = i[1]
719		bottom = i[2]
720		ok = true
721	case 4: //nolint:mnd
722		top = i[0]
723		right = i[1]
724		bottom = i[2]
725		left = i[3]
726		ok = true
727	}
728	return top, right, bottom, left, ok
729}
730
731// whichSidesBool is like whichSidesInt, except it operates on a series of
732// boolean values. See the comment on whichSidesInt for details on how this
733// works.
734func whichSidesBool(i ...bool) (top, right, bottom, left bool, ok bool) {
735	switch len(i) {
736	case 1:
737		top = i[0]
738		bottom = i[0]
739		left = i[0]
740		right = i[0]
741		ok = true
742	case 2: //nolint:mnd
743		top = i[0]
744		bottom = i[0]
745		left = i[1]
746		right = i[1]
747		ok = true
748	case 3: //nolint:mnd
749		top = i[0]
750		left = i[1]
751		right = i[1]
752		bottom = i[2]
753		ok = true
754	case 4: //nolint:mnd
755		top = i[0]
756		right = i[1]
757		bottom = i[2]
758		left = i[3]
759		ok = true
760	}
761	return top, right, bottom, left, ok
762}
763
764// whichSidesColor is like whichSides, except it operates on a series of
765// boolean values. See the comment on whichSidesInt for details on how this
766// works.
767func whichSidesColor(i ...color.Color) (top, right, bottom, left color.Color, ok bool) {
768	switch len(i) {
769	case 1:
770		top = i[0]
771		bottom = i[0]
772		left = i[0]
773		right = i[0]
774		ok = true
775	case 2: //nolint:mnd
776		top = i[0]
777		bottom = i[0]
778		left = i[1]
779		right = i[1]
780		ok = true
781	case 3: //nolint:mnd
782		top = i[0]
783		left = i[1]
784		right = i[1]
785		bottom = i[2]
786		ok = true
787	case 4: //nolint:mnd
788		top = i[0]
789		right = i[1]
790		bottom = i[2]
791		left = i[3]
792		ok = true
793	}
794	return top, right, bottom, left, ok
795}