main
Raw Download raw file
  1package lipgloss
  2
  3import (
  4	"math"
  5	"strings"
  6
  7	"github.com/charmbracelet/x/ansi"
  8)
  9
 10// Position represents a position along a horizontal or vertical axis. It's in
 11// situations where an axis is involved, like alignment, joining, placement and
 12// so on.
 13//
 14// A value of 0 represents the start (the left or top) and 1 represents the end
 15// (the right or bottom). 0.5 represents the center.
 16//
 17// There are constants Top, Bottom, Center, Left and Right in this package that
 18// can be used to aid readability.
 19type Position float64
 20
 21func (p Position) value() float64 {
 22	return math.Min(1, math.Max(0, float64(p)))
 23}
 24
 25// Position aliases.
 26const (
 27	Top    Position = 0.0
 28	Bottom Position = 1.0
 29	Center Position = 0.5
 30	Left   Position = 0.0
 31	Right  Position = 1.0
 32)
 33
 34// Place places a string or text block vertically in an unstyled box of a given
 35// width or height.
 36func Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
 37	return renderer.Place(width, height, hPos, vPos, str, opts...)
 38}
 39
 40// Place places a string or text block vertically in an unstyled box of a given
 41// width or height.
 42func (r *Renderer) Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
 43	return r.PlaceVertical(height, vPos, r.PlaceHorizontal(width, hPos, str, opts...), opts...)
 44}
 45
 46// PlaceHorizontal places a string or text block horizontally in an unstyled
 47// block of a given width. If the given width is shorter than the max width of
 48// the string (measured by its longest line) this will be a noop.
 49func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
 50	return renderer.PlaceHorizontal(width, pos, str, opts...)
 51}
 52
 53// PlaceHorizontal places a string or text block horizontally in an unstyled
 54// block of a given width. If the given width is shorter than the max width of
 55// the string (measured by its longest line) this will be a noöp.
 56func (r *Renderer) PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
 57	lines, contentWidth := getLines(str)
 58	gap := width - contentWidth
 59
 60	if gap <= 0 {
 61		return str
 62	}
 63
 64	ws := newWhitespace(r, opts...)
 65
 66	var b strings.Builder
 67	for i, l := range lines {
 68		// Is this line shorter than the longest line?
 69		short := max(0, contentWidth-ansi.StringWidth(l))
 70
 71		switch pos { //nolint:exhaustive
 72		case Left:
 73			b.WriteString(l)
 74			b.WriteString(ws.render(gap + short))
 75
 76		case Right:
 77			b.WriteString(ws.render(gap + short))
 78			b.WriteString(l)
 79
 80		default: // somewhere in the middle
 81			totalGap := gap + short
 82
 83			split := int(math.Round(float64(totalGap) * pos.value()))
 84			left := totalGap - split
 85			right := totalGap - left
 86
 87			b.WriteString(ws.render(left))
 88			b.WriteString(l)
 89			b.WriteString(ws.render(right))
 90		}
 91
 92		if i < len(lines)-1 {
 93			b.WriteRune('\n')
 94		}
 95	}
 96
 97	return b.String()
 98}
 99
100// PlaceVertical places a string or text block vertically in an unstyled block
101// of a given height. If the given height is shorter than the height of the
102// string (measured by its newlines) then this will be a noop.
103func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
104	return renderer.PlaceVertical(height, pos, str, opts...)
105}
106
107// PlaceVertical places a string or text block vertically in an unstyled block
108// of a given height. If the given height is shorter than the height of the
109// string (measured by its newlines) then this will be a noöp.
110func (r *Renderer) PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
111	contentHeight := strings.Count(str, "\n") + 1
112	gap := height - contentHeight
113
114	if gap <= 0 {
115		return str
116	}
117
118	ws := newWhitespace(r, opts...)
119
120	_, width := getLines(str)
121	emptyLine := ws.render(width)
122	b := strings.Builder{}
123
124	switch pos { //nolint:exhaustive
125	case Top:
126		b.WriteString(str)
127		b.WriteRune('\n')
128		for i := 0; i < gap; i++ {
129			b.WriteString(emptyLine)
130			if i < gap-1 {
131				b.WriteRune('\n')
132			}
133		}
134
135	case Bottom:
136		b.WriteString(strings.Repeat(emptyLine+"\n", gap))
137		b.WriteString(str)
138
139	default: // Somewhere in the middle
140		split := int(math.Round(float64(gap) * pos.value()))
141		top := gap - split
142		bottom := gap - top
143
144		b.WriteString(strings.Repeat(emptyLine+"\n", top))
145		b.WriteString(str)
146
147		for i := 0; i < bottom; i++ {
148			b.WriteRune('\n')
149			b.WriteString(emptyLine)
150		}
151	}
152
153	return b.String()
154}